mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 12:47:35 +00:00
LibWeb: Bring border painting much closer to the spec/other browsers
This commit adds some much nicer border painting, which now supports: - Elliptical corners - Blending between different border thicknesses, with rounded corners - Anti-aliasing There are some little TODOs left to tackle: - Painting the corners with line styles other than solid - Blending between colors on the corners (see comments) The painting requires allocating a small bitmap, that only fits the corners (so in most cases this is very small). This bitmap is then cached so for all paints but the first there will be no further allocations.
This commit is contained in:
parent
411a815c3b
commit
9e71fa9aa7
6 changed files with 175 additions and 193 deletions
|
@ -42,16 +42,9 @@ 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);
|
||||||
|
|
||||||
auto border_radius_to_corner = [&](BorderRadiusData const& border_radius) {
|
|
||||||
return Gfx::AntiAliasingPainter::CornerRadius {
|
|
||||||
static_cast<int>(border_radius.horizontal_radius),
|
|
||||||
static_cast<int>(border_radius.vertical_radius)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Gfx::AntiAliasingPainter aa_painter { painter };
|
Gfx::AntiAliasingPainter aa_painter { painter };
|
||||||
aa_painter.fill_rect_with_rounded_corners(color_rect.to_rounded<int>(),
|
aa_painter.fill_rect_with_rounded_corners(color_rect.to_rounded<int>(),
|
||||||
background_color, border_radius_to_corner(border_radii.top_left), border_radius_to_corner(border_radii.top_right), border_radius_to_corner(border_radii.bottom_right), border_radius_to_corner(border_radii.bottom_left));
|
background_color, border_radii.top_left.as_corner(), border_radii.top_right.as_corner(), border_radii.bottom_right.as_corner(), border_radii.bottom_left.as_corner());
|
||||||
|
|
||||||
if (!background_layers)
|
if (!background_layers)
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||||
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
|
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
|
||||||
|
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <LibGfx/AntiAliasingPainter.h>
|
||||||
#include <LibGfx/Painter.h>
|
#include <LibGfx/Painter.h>
|
||||||
#include <LibWeb/Painting/BorderPainting.h>
|
#include <LibWeb/Painting/BorderPainting.h>
|
||||||
#include <LibWeb/Painting/PaintContext.h>
|
#include <LibWeb/Painting/PaintContext.h>
|
||||||
|
|
||||||
namespace Web::Painting {
|
namespace Web::Painting {
|
||||||
|
|
||||||
BorderRadiiData normalized_border_radii_data(Layout::Node const& node, Gfx::FloatRect const& rect, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius, RelativeToWidthOnly relative_to_width_only)
|
BorderRadiiData normalized_border_radii_data(Layout::Node const& node, Gfx::FloatRect const& rect, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius)
|
||||||
{
|
{
|
||||||
BorderRadiusData bottom_left_radius_px {};
|
BorderRadiusData bottom_left_radius_px {};
|
||||||
BorderRadiusData bottom_right_radius_px {};
|
BorderRadiusData bottom_right_radius_px {};
|
||||||
|
@ -24,28 +26,11 @@ BorderRadiiData normalized_border_radii_data(Layout::Node const& node, Gfx::Floa
|
||||||
top_left_radius_px.horizontal_radius = top_left_radius.horizontal_radius.resolved(node, width_length).to_px(node);
|
top_left_radius_px.horizontal_radius = top_left_radius.horizontal_radius.resolved(node, width_length).to_px(node);
|
||||||
top_right_radius_px.horizontal_radius = top_right_radius.horizontal_radius.resolved(node, width_length).to_px(node);
|
top_right_radius_px.horizontal_radius = top_right_radius.horizontal_radius.resolved(node, width_length).to_px(node);
|
||||||
|
|
||||||
// FIXME: Remove `relative_to_width_only = Yes' flag, this only exists to
|
|
||||||
// avoid overlapping curves for (outline) borders, which do not yet
|
|
||||||
// support elliptical corners.
|
|
||||||
switch (relative_to_width_only) {
|
|
||||||
case RelativeToWidthOnly::No: {
|
|
||||||
// Normal correct rendering:
|
|
||||||
auto height_length = CSS::Length::make_px(rect.height());
|
auto height_length = CSS::Length::make_px(rect.height());
|
||||||
bottom_left_radius_px.vertical_radius = bottom_left_radius.vertical_radius.resolved(node, height_length).to_px(node);
|
bottom_left_radius_px.vertical_radius = bottom_left_radius.vertical_radius.resolved(node, height_length).to_px(node);
|
||||||
bottom_right_radius_px.vertical_radius = bottom_right_radius.vertical_radius.resolved(node, height_length).to_px(node);
|
bottom_right_radius_px.vertical_radius = bottom_right_radius.vertical_radius.resolved(node, height_length).to_px(node);
|
||||||
top_left_radius_px.vertical_radius = top_left_radius.vertical_radius.resolved(node, height_length).to_px(node);
|
top_left_radius_px.vertical_radius = top_left_radius.vertical_radius.resolved(node, height_length).to_px(node);
|
||||||
top_right_radius_px.vertical_radius = top_right_radius.vertical_radius.resolved(node, height_length).to_px(node);
|
top_right_radius_px.vertical_radius = top_right_radius.vertical_radius.resolved(node, height_length).to_px(node);
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RelativeToWidthOnly::Yes:
|
|
||||||
bottom_left_radius_px.vertical_radius = bottom_left_radius_px.horizontal_radius;
|
|
||||||
bottom_right_radius_px.vertical_radius = bottom_right_radius_px.horizontal_radius;
|
|
||||||
top_left_radius_px.vertical_radius = top_left_radius_px.horizontal_radius;
|
|
||||||
top_right_radius_px.vertical_radius = top_right_radius_px.horizontal_radius;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scale overlapping curves according to https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
|
// Scale overlapping curves according to https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
|
||||||
auto f = 1.0f;
|
auto f = 1.0f;
|
||||||
|
@ -70,10 +55,8 @@ BorderRadiiData normalized_border_radii_data(Layout::Node const& node, Gfx::Floa
|
||||||
return BorderRadiiData { top_left_radius_px, top_right_radius_px, bottom_right_radius_px, bottom_left_radius_px };
|
return BorderRadiiData { top_left_radius_px, top_right_radius_px, bottom_right_radius_px, bottom_left_radius_px };
|
||||||
}
|
}
|
||||||
|
|
||||||
void paint_border(PaintContext& context, BorderEdge edge, Gfx::FloatRect const& a_rect, BorderRadiiData const& border_radii_data, BordersData const& borders_data)
|
void paint_border(PaintContext& context, BorderEdge edge, Gfx::IntRect const& rect, BordersData const& borders_data)
|
||||||
{
|
{
|
||||||
auto rect = a_rect.to_rounded<float>();
|
|
||||||
|
|
||||||
auto const& border_data = [&] {
|
auto const& border_data = [&] {
|
||||||
switch (edge) {
|
switch (edge) {
|
||||||
case BorderEdge::Top:
|
case BorderEdge::Top:
|
||||||
|
@ -93,14 +76,14 @@ void paint_border(PaintContext& context, BorderEdge edge, Gfx::FloatRect const&
|
||||||
|
|
||||||
auto color = border_data.color;
|
auto color = border_data.color;
|
||||||
auto border_style = border_data.line_style;
|
auto border_style = border_data.line_style;
|
||||||
int int_width = max((int)width, 1);
|
int int_width = ceil(width);
|
||||||
|
|
||||||
struct Points {
|
struct Points {
|
||||||
Gfx::FloatPoint p1;
|
Gfx::IntPoint p1;
|
||||||
Gfx::FloatPoint p2;
|
Gfx::IntPoint p2;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto points_for_edge = [](BorderEdge edge, Gfx::FloatRect const& rect) -> Points {
|
auto points_for_edge = [](BorderEdge edge, Gfx::IntRect const& rect) -> Points {
|
||||||
switch (edge) {
|
switch (edge) {
|
||||||
case BorderEdge::Top:
|
case BorderEdge::Top:
|
||||||
return { rect.top_left(), rect.top_right() };
|
return { rect.top_left(), rect.top_right() };
|
||||||
|
@ -150,174 +133,167 @@ void paint_border(PaintContext& context, BorderEdge edge, Gfx::FloatRect const&
|
||||||
p2.translate_by(int_width / 2, -int_width / 2);
|
p2.translate_by(int_width / 2, -int_width / 2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
context.painter().draw_line({ (int)p1.x(), (int)p1.y() }, { (int)p2.x(), (int)p2.y() }, color, int_width, gfx_line_style);
|
context.painter().draw_line(p1, p2, color, int_width, gfx_line_style);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto draw_line = [&](auto& p1, auto& p2) {
|
context.painter().fill_rect(rect, color);
|
||||||
context.painter().draw_line({ (int)p1.x(), (int)p1.y() }, { (int)p2.x(), (int)p2.y() }, color, 1, gfx_line_style);
|
|
||||||
};
|
|
||||||
|
|
||||||
float p1_step = 0;
|
|
||||||
float p2_step = 0;
|
|
||||||
|
|
||||||
bool has_top_left_radius = border_radii_data.top_left.horizontal_radius > 0;
|
|
||||||
bool has_top_right_radius = border_radii_data.top_right.horizontal_radius > 0;
|
|
||||||
bool has_bottom_left_radius = border_radii_data.bottom_left.horizontal_radius > 0;
|
|
||||||
bool has_bottom_right_radius = border_radii_data.bottom_right.horizontal_radius > 0;
|
|
||||||
|
|
||||||
switch (edge) {
|
|
||||||
case BorderEdge::Top:
|
|
||||||
p1_step = has_top_left_radius ? 0 : borders_data.left.width / (float)int_width;
|
|
||||||
p2_step = has_top_right_radius ? 0 : borders_data.right.width / (float)int_width;
|
|
||||||
for (int i = 0; i < int_width; ++i) {
|
|
||||||
draw_line(p1, p2);
|
|
||||||
p1.translate_by(p1_step, 1);
|
|
||||||
p2.translate_by(-p2_step, 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BorderEdge::Right:
|
|
||||||
p1_step = has_top_right_radius ? 0 : borders_data.top.width / (float)int_width;
|
|
||||||
p2_step = has_bottom_right_radius ? 0 : borders_data.bottom.width / (float)int_width;
|
|
||||||
for (int i = int_width - 1; i >= 0; --i) {
|
|
||||||
draw_line(p1, p2);
|
|
||||||
p1.translate_by(-1, p1_step);
|
|
||||||
p2.translate_by(-1, -p2_step);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BorderEdge::Bottom:
|
|
||||||
p1_step = has_bottom_left_radius ? 0 : borders_data.left.width / (float)int_width;
|
|
||||||
p2_step = has_bottom_right_radius ? 0 : borders_data.right.width / (float)int_width;
|
|
||||||
for (int i = int_width - 1; i >= 0; --i) {
|
|
||||||
draw_line(p1, p2);
|
|
||||||
p1.translate_by(p1_step, -1);
|
|
||||||
p2.translate_by(-p2_step, -1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BorderEdge::Left:
|
|
||||||
p1_step = has_top_left_radius ? 0 : borders_data.top.width / (float)int_width;
|
|
||||||
p2_step = has_bottom_left_radius ? 0 : borders_data.bottom.width / (float)int_width;
|
|
||||||
for (int i = 0; i < int_width; ++i) {
|
|
||||||
draw_line(p1, p2);
|
|
||||||
p1.translate_by(1, p1_step);
|
|
||||||
p2.translate_by(1, -p2_step);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void paint_all_borders(PaintContext& context, Gfx::FloatRect const& bordered_rect, BorderRadiiData const& border_radii_data, BordersData const& borders_data)
|
void paint_all_borders(PaintContext& context, Gfx::FloatRect const& bordered_rect, BorderRadiiData const& border_radii_data, BordersData const& borders_data)
|
||||||
{
|
{
|
||||||
auto const border_rect = bordered_rect;
|
if (borders_data.top.width <= 0 && borders_data.right.width <= 0 && borders_data.left.width <= 0 && borders_data.bottom.width <= 0)
|
||||||
|
|
||||||
// FIXME: Support elliptical border radii.
|
|
||||||
auto const top_left_radius = border_radii_data.top_left.horizontal_radius;
|
|
||||||
auto const top_right_radius = border_radii_data.top_right.horizontal_radius;
|
|
||||||
auto const bottom_right_radius = border_radii_data.bottom_right.horizontal_radius;
|
|
||||||
auto const bottom_left_radius = border_radii_data.bottom_left.horizontal_radius;
|
|
||||||
|
|
||||||
Gfx::FloatRect top_border_rect = {
|
|
||||||
border_rect.x() + top_left_radius,
|
|
||||||
border_rect.y(),
|
|
||||||
border_rect.width() - top_left_radius - top_right_radius,
|
|
||||||
border_rect.height()
|
|
||||||
};
|
|
||||||
Gfx::FloatRect right_border_rect = {
|
|
||||||
border_rect.x(),
|
|
||||||
border_rect.y() + top_right_radius,
|
|
||||||
border_rect.width(),
|
|
||||||
border_rect.height() - top_right_radius - bottom_right_radius
|
|
||||||
};
|
|
||||||
Gfx::FloatRect bottom_border_rect = {
|
|
||||||
border_rect.x() + bottom_left_radius,
|
|
||||||
border_rect.y(),
|
|
||||||
border_rect.width() - bottom_left_radius - bottom_right_radius,
|
|
||||||
border_rect.height()
|
|
||||||
};
|
|
||||||
Gfx::FloatRect left_border_rect = {
|
|
||||||
border_rect.x(),
|
|
||||||
border_rect.y() + top_left_radius,
|
|
||||||
border_rect.width(),
|
|
||||||
border_rect.height() - top_left_radius - bottom_left_radius
|
|
||||||
};
|
|
||||||
|
|
||||||
Painting::paint_border(context, Painting::BorderEdge::Top, top_border_rect, border_radii_data, borders_data);
|
|
||||||
Painting::paint_border(context, Painting::BorderEdge::Right, right_border_rect, border_radii_data, borders_data);
|
|
||||||
Painting::paint_border(context, Painting::BorderEdge::Bottom, bottom_border_rect, border_radii_data, borders_data);
|
|
||||||
Painting::paint_border(context, Painting::BorderEdge::Left, left_border_rect, border_radii_data, borders_data);
|
|
||||||
|
|
||||||
// Draws a quarter circle clockwise
|
|
||||||
auto draw_quarter_circle = [&](Gfx::FloatPoint const& from, Gfx::FloatPoint const& to, Gfx::Color color, int thickness) {
|
|
||||||
Gfx::FloatPoint center = { 0, 0 };
|
|
||||||
Gfx::FloatPoint offset = { 0, 0 };
|
|
||||||
Gfx::FloatPoint circle_position = { 0, 0 };
|
|
||||||
|
|
||||||
auto radius = fabsf(from.x() - to.x());
|
|
||||||
|
|
||||||
if (from.x() < to.x() && from.y() > to.y()) {
|
|
||||||
// top-left
|
|
||||||
center.set_x(radius);
|
|
||||||
center.set_y(radius);
|
|
||||||
offset.set_y(1);
|
|
||||||
} else if (from.x() < to.x() && from.y() < to.y()) {
|
|
||||||
// top-right
|
|
||||||
circle_position.set_x(from.x());
|
|
||||||
center.set_y(radius);
|
|
||||||
offset.set_x(-1);
|
|
||||||
offset.set_y(1);
|
|
||||||
} else if (from.x() > to.x() && from.y() < to.y()) {
|
|
||||||
// bottom-right
|
|
||||||
circle_position.set_x(to.x());
|
|
||||||
circle_position.set_y(from.y());
|
|
||||||
offset.set_x(-1);
|
|
||||||
} else if (from.x() > to.x() && from.y() > to.y()) {
|
|
||||||
// bottom-left
|
|
||||||
circle_position.set_y(to.y());
|
|
||||||
center.set_x(radius);
|
|
||||||
} else {
|
|
||||||
// You are lying about your intentions of drawing a quarter circle, your coordinates are (partly) the same!
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Gfx::FloatRect circle_rect = {
|
Gfx::IntRect border_rect = bordered_rect.to_rounded<int>();
|
||||||
border_rect.x() + circle_position.x(),
|
|
||||||
border_rect.y() + circle_position.y(),
|
auto top_left = border_radii_data.top_left.as_corner();
|
||||||
radius,
|
auto top_right = border_radii_data.top_right.as_corner();
|
||||||
radius
|
auto bottom_right = border_radii_data.bottom_right.as_corner();
|
||||||
|
auto bottom_left = border_radii_data.bottom_left.as_corner();
|
||||||
|
|
||||||
|
// Disable border radii if the corresponding borders don't exist:
|
||||||
|
if (borders_data.bottom.width <= 0 && borders_data.left.width <= 0)
|
||||||
|
bottom_left = { 0, 0 };
|
||||||
|
if (borders_data.bottom.width <= 0 && borders_data.right.width <= 0)
|
||||||
|
bottom_right = { 0, 0 };
|
||||||
|
if (borders_data.top.width <= 0 && borders_data.left.width <= 0)
|
||||||
|
top_left = { 0, 0 };
|
||||||
|
if (borders_data.top.width <= 0 && borders_data.right.width <= 0)
|
||||||
|
top_right = { 0, 0 };
|
||||||
|
|
||||||
|
auto int_width = [&](auto value) -> int {
|
||||||
|
return ceil(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
context.painter().draw_circle_arc_intersecting(
|
Gfx::IntRect top_border_rect = {
|
||||||
Gfx::enclosing_int_rect(circle_rect),
|
border_rect.x() + top_left.horizontal_radius,
|
||||||
(center + offset).to_rounded<int>(),
|
border_rect.y(),
|
||||||
radius,
|
border_rect.width() - top_left.horizontal_radius - top_right.horizontal_radius,
|
||||||
color,
|
int_width(borders_data.top.width)
|
||||||
thickness);
|
};
|
||||||
|
Gfx::IntRect right_border_rect = {
|
||||||
|
border_rect.x() + (border_rect.width() - int_width(borders_data.right.width)),
|
||||||
|
border_rect.y() + top_right.vertical_radius,
|
||||||
|
int_width(borders_data.right.width),
|
||||||
|
border_rect.height() - top_right.vertical_radius - bottom_right.vertical_radius
|
||||||
|
};
|
||||||
|
Gfx::IntRect bottom_border_rect = {
|
||||||
|
border_rect.x() + bottom_left.horizontal_radius,
|
||||||
|
border_rect.y() + (border_rect.height() - int_width(borders_data.bottom.width)),
|
||||||
|
border_rect.width() - bottom_left.horizontal_radius - bottom_right.horizontal_radius,
|
||||||
|
int_width(borders_data.bottom.width)
|
||||||
|
};
|
||||||
|
Gfx::IntRect left_border_rect = {
|
||||||
|
border_rect.x(),
|
||||||
|
border_rect.y() + top_left.vertical_radius,
|
||||||
|
int_width(borders_data.left.width),
|
||||||
|
border_rect.height() - top_left.vertical_radius - bottom_left.vertical_radius
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: Which color to use?
|
// Avoid overlapping pixels on the edges.
|
||||||
if (top_left_radius != 0) {
|
if (!top_left)
|
||||||
Gfx::FloatPoint arc_start = { 0, top_left_radius };
|
top_border_rect.shrink(0, 0, 0, left_border_rect.width());
|
||||||
Gfx::FloatPoint arc_end = { top_left_radius, 0 };
|
if (!top_right)
|
||||||
draw_quarter_circle(arc_start, arc_end, borders_data.top.color, borders_data.top.width);
|
top_border_rect.shrink(0, right_border_rect.width(), 0, 0);
|
||||||
}
|
if (!bottom_left)
|
||||||
|
bottom_border_rect.shrink(0, 0, 0, left_border_rect.width());
|
||||||
|
if (!bottom_right)
|
||||||
|
bottom_border_rect.shrink(0, right_border_rect.width(), 0, 0);
|
||||||
|
|
||||||
if (top_right_radius != 0) {
|
auto border_color_no_alpha = borders_data.top.color;
|
||||||
Gfx::FloatPoint arc_start = { top_left_radius + top_border_rect.width(), 0 };
|
border_color_no_alpha.set_alpha(255);
|
||||||
Gfx::FloatPoint arc_end = { bordered_rect.width(), top_right_radius };
|
|
||||||
draw_quarter_circle(arc_start, arc_end, borders_data.top.color, borders_data.top.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bottom_right_radius != 0) {
|
// Paint the strait line part of the border:
|
||||||
Gfx::FloatPoint arc_start = { bordered_rect.width(), top_right_radius + right_border_rect.height() };
|
Painting::paint_border(context, Painting::BorderEdge::Top, top_border_rect, borders_data);
|
||||||
Gfx::FloatPoint arc_end = { bottom_border_rect.width() + bottom_left_radius, bordered_rect.height() };
|
Painting::paint_border(context, Painting::BorderEdge::Right, right_border_rect, borders_data);
|
||||||
draw_quarter_circle(arc_start, arc_end, borders_data.bottom.color, borders_data.bottom.width);
|
Painting::paint_border(context, Painting::BorderEdge::Bottom, bottom_border_rect, borders_data);
|
||||||
}
|
Painting::paint_border(context, Painting::BorderEdge::Left, left_border_rect, borders_data);
|
||||||
|
|
||||||
if (bottom_left_radius != 0) {
|
// Cache the smallest possible bitmap to render just the corners for the border.
|
||||||
Gfx::FloatPoint arc_start = { bottom_left_radius, bordered_rect.height() };
|
auto expand_width = abs(int_width(borders_data.left.width) - int_width(borders_data.right.width));
|
||||||
Gfx::FloatPoint arc_end = { 0, bordered_rect.height() - bottom_left_radius };
|
auto expand_height = abs(int_width(borders_data.top.width) - int_width(borders_data.bottom.width));
|
||||||
draw_quarter_circle(arc_start, arc_end, borders_data.bottom.color, borders_data.bottom.width);
|
Gfx::IntRect corner_mask_rect {
|
||||||
|
0, 0,
|
||||||
|
max(
|
||||||
|
top_left.horizontal_radius + top_right.horizontal_radius + expand_width,
|
||||||
|
bottom_left.horizontal_radius + bottom_right.horizontal_radius + expand_height),
|
||||||
|
max(
|
||||||
|
top_left.vertical_radius + bottom_left.vertical_radius + expand_width,
|
||||||
|
top_right.vertical_radius + bottom_right.vertical_radius + expand_height)
|
||||||
|
};
|
||||||
|
auto allocate_mask_bitmap = [&]() -> RefPtr<Gfx::Bitmap> {
|
||||||
|
auto bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, corner_mask_rect.size());
|
||||||
|
if (!bitmap.is_error())
|
||||||
|
return bitmap.release_value();
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
static thread_local auto corner_bitmap = allocate_mask_bitmap();
|
||||||
|
|
||||||
|
// Only reallocate the corner bitmap is the existing one is too small.
|
||||||
|
// (should mean no more allocations after the first paint -- amortised zero allocations :^))
|
||||||
|
Gfx::Painter painter = ({
|
||||||
|
Optional<Gfx::Painter> painter;
|
||||||
|
if (corner_bitmap && corner_bitmap->rect().contains(corner_mask_rect)) {
|
||||||
|
painter = Gfx::Painter { *corner_bitmap };
|
||||||
|
painter->clear_rect(corner_mask_rect, Gfx::Color());
|
||||||
|
} else {
|
||||||
|
corner_bitmap = allocate_mask_bitmap();
|
||||||
|
if (!corner_bitmap)
|
||||||
|
return dbgln("Failed to allocate border corner bitmap with size {}", corner_mask_rect.size());
|
||||||
|
painter = Gfx::Painter { *corner_bitmap };
|
||||||
}
|
}
|
||||||
|
*painter;
|
||||||
|
});
|
||||||
|
|
||||||
|
Gfx::AntiAliasingPainter aa_painter { painter };
|
||||||
|
|
||||||
|
// Paint a little tile sheet for the corners
|
||||||
|
// TODO: Support various line styles on the corners (dotted, dashes, etc)
|
||||||
|
|
||||||
|
// Paint the outer (minimal) corner rounded rectangle:
|
||||||
|
aa_painter.fill_rect_with_rounded_corners(corner_mask_rect, border_color_no_alpha, top_left, top_right, bottom_right, bottom_left);
|
||||||
|
|
||||||
|
// Subtract the inner corner rectangle:
|
||||||
|
auto inner_corner_mask_rect = corner_mask_rect.shrunken(
|
||||||
|
int_width(borders_data.top.width),
|
||||||
|
int_width(borders_data.right.width),
|
||||||
|
int_width(borders_data.bottom.width),
|
||||||
|
int_width(borders_data.left.width));
|
||||||
|
auto inner_top_left = top_left;
|
||||||
|
auto inner_top_right = top_right;
|
||||||
|
auto inner_bottom_right = bottom_right;
|
||||||
|
auto inner_bottom_left = bottom_left;
|
||||||
|
inner_top_left.horizontal_radius = max(0, inner_top_left.horizontal_radius - int_width(borders_data.left.width));
|
||||||
|
inner_top_left.vertical_radius = max(0, inner_top_left.vertical_radius - int_width(borders_data.top.width));
|
||||||
|
inner_top_right.horizontal_radius = max(0, inner_top_right.horizontal_radius - int_width(borders_data.right.width));
|
||||||
|
inner_top_right.vertical_radius = max(0, inner_top_right.vertical_radius - int_width(borders_data.top.width));
|
||||||
|
inner_bottom_right.horizontal_radius = max(0, inner_bottom_right.horizontal_radius - int_width(borders_data.right.width));
|
||||||
|
inner_bottom_right.vertical_radius = max(0, inner_bottom_right.vertical_radius - int_width(borders_data.bottom.width));
|
||||||
|
inner_bottom_left.horizontal_radius = max(0, inner_bottom_left.horizontal_radius - int_width(borders_data.left.width));
|
||||||
|
inner_bottom_left.vertical_radius = max(0, inner_bottom_left.vertical_radius - int_width(borders_data.bottom.width));
|
||||||
|
aa_painter.fill_rect_with_rounded_corners(inner_corner_mask_rect, border_color_no_alpha, inner_top_left, inner_top_right, inner_bottom_right, inner_bottom_left, Gfx::AntiAliasingPainter::BlendMode::AlphaSubtract);
|
||||||
|
|
||||||
|
// TODO: Support dual color corners. Other browsers will render a rounded corner between two borders of
|
||||||
|
// different colors using both colours, normally split at a 45 degree angle (though the exact angle is interpolated).
|
||||||
|
auto blit_corner = [&](Gfx::IntPoint const& position, Gfx::IntRect const& src_rect, Color corner_color) {
|
||||||
|
context.painter().blit_filtered(position, *corner_bitmap, src_rect, [&](auto const& corner_pixel) {
|
||||||
|
return corner_color.with_alpha((corner_color.alpha() * corner_pixel.alpha()) / 255);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Blit the corners into to their corresponding locations:
|
||||||
|
if (top_left)
|
||||||
|
blit_corner(border_rect.top_left(), top_left.as_rect(), borders_data.top.color);
|
||||||
|
|
||||||
|
if (top_right)
|
||||||
|
blit_corner(border_rect.top_right().translated(-top_right.horizontal_radius + 1, 0), top_right.as_rect().translated(corner_mask_rect.width() - top_right.horizontal_radius, 0), borders_data.top.color);
|
||||||
|
|
||||||
|
if (bottom_right)
|
||||||
|
blit_corner(border_rect.bottom_right().translated(-bottom_right.horizontal_radius + 1, -bottom_right.vertical_radius + 1), bottom_right.as_rect().translated(corner_mask_rect.width() - bottom_right.horizontal_radius, corner_mask_rect.height() - bottom_right.vertical_radius), borders_data.bottom.color);
|
||||||
|
|
||||||
|
if (bottom_left)
|
||||||
|
blit_corner(border_rect.bottom_left().translated(0, -bottom_left.vertical_radius + 1), bottom_left.as_rect().translated(0, corner_mask_rect.height() - bottom_left.vertical_radius), borders_data.bottom.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibGfx/AntiAliasingPainter.h>
|
||||||
#include <LibGfx/Forward.h>
|
#include <LibGfx/Forward.h>
|
||||||
#include <LibWeb/CSS/ComputedValues.h>
|
#include <LibWeb/CSS/ComputedValues.h>
|
||||||
|
|
||||||
|
@ -14,6 +15,19 @@ namespace Web::Painting {
|
||||||
struct BorderRadiusData {
|
struct BorderRadiusData {
|
||||||
float horizontal_radius { 0 };
|
float horizontal_radius { 0 };
|
||||||
float vertical_radius { 0 };
|
float vertical_radius { 0 };
|
||||||
|
|
||||||
|
Gfx::AntiAliasingPainter::CornerRadius as_corner() const
|
||||||
|
{
|
||||||
|
return Gfx::AntiAliasingPainter::CornerRadius {
|
||||||
|
static_cast<int>(horizontal_radius),
|
||||||
|
static_cast<int>(vertical_radius)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
inline operator bool() const
|
||||||
|
{
|
||||||
|
return static_cast<int>(horizontal_radius) > 0 && static_cast<int>(vertical_radius) > 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BorderRadiiData {
|
struct BorderRadiiData {
|
||||||
|
@ -21,14 +35,14 @@ struct BorderRadiiData {
|
||||||
BorderRadiusData top_right;
|
BorderRadiusData top_right;
|
||||||
BorderRadiusData bottom_right;
|
BorderRadiusData bottom_right;
|
||||||
BorderRadiusData bottom_left;
|
BorderRadiusData bottom_left;
|
||||||
|
|
||||||
|
inline bool has_any_radius() const
|
||||||
|
{
|
||||||
|
return top_left || top_right || bottom_right || bottom_left;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class RelativeToWidthOnly {
|
BorderRadiiData normalized_border_radii_data(Layout::Node const&, Gfx::FloatRect const&, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius);
|
||||||
Yes,
|
|
||||||
No
|
|
||||||
};
|
|
||||||
|
|
||||||
BorderRadiiData normalized_border_radii_data(Layout::Node const&, Gfx::FloatRect const&, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius, RelativeToWidthOnly relative_to_width_only = RelativeToWidthOnly::No);
|
|
||||||
|
|
||||||
enum class BorderEdge {
|
enum class BorderEdge {
|
||||||
Top,
|
Top,
|
||||||
|
@ -42,7 +56,7 @@ struct BordersData {
|
||||||
CSS::BorderData bottom;
|
CSS::BorderData bottom;
|
||||||
CSS::BorderData left;
|
CSS::BorderData left;
|
||||||
};
|
};
|
||||||
void paint_border(PaintContext& context, BorderEdge edge, Gfx::FloatRect const& rect, BorderRadiiData const& border_radii_data, BordersData const& borders_data);
|
void paint_border(PaintContext& context, BorderEdge edge, Gfx::IntRect const& rect, BordersData const& borders_data);
|
||||||
void paint_all_borders(PaintContext& context, Gfx::FloatRect const& bordered_rect, BorderRadiiData const& border_radii_data, BordersData const&);
|
void paint_all_borders(PaintContext& context, Gfx::FloatRect const& bordered_rect, BorderRadiiData const& border_radii_data, BordersData const&);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,7 @@ void InlinePaintable::paint(PaintContext& context, Painting::PaintPhase phase) c
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bordered_rect = absolute_fragment_rect.inflated(borders_data.top.width, borders_data.right.width, borders_data.bottom.width, borders_data.left.width);
|
auto bordered_rect = absolute_fragment_rect.inflated(borders_data.top.width, borders_data.right.width, borders_data.bottom.width, borders_data.left.width);
|
||||||
auto border_radii_data = Painting::normalized_border_radii_data(layout_node(), bordered_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius, Painting::RelativeToWidthOnly::Yes);
|
auto border_radii_data = Painting::normalized_border_radii_data(layout_node(), bordered_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius);
|
||||||
|
|
||||||
Painting::paint_all_borders(context, bordered_rect, border_radii_data, borders_data);
|
Painting::paint_all_borders(context, bordered_rect, border_radii_data, borders_data);
|
||||||
|
|
||||||
|
|
|
@ -178,7 +178,7 @@ void PaintableBox::paint_border(PaintContext& context) const
|
||||||
.bottom = computed_values().border_bottom(),
|
.bottom = computed_values().border_bottom(),
|
||||||
.left = computed_values().border_left(),
|
.left = computed_values().border_left(),
|
||||||
};
|
};
|
||||||
paint_all_borders(context, absolute_border_box_rect(), normalized_border_radii_data(Painting::RelativeToWidthOnly::Yes), borders_data);
|
paint_all_borders(context, absolute_border_box_rect(), normalized_border_radii_data(), borders_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PaintableBox::paint_background(PaintContext& context) const
|
void PaintableBox::paint_background(PaintContext& context) const
|
||||||
|
@ -233,14 +233,13 @@ void PaintableBox::paint_box_shadow(PaintContext& context) const
|
||||||
Painting::paint_box_shadow(context, enclosing_int_rect(absolute_border_box_rect()), resolved_box_shadow_data);
|
Painting::paint_box_shadow(context, enclosing_int_rect(absolute_border_box_rect()), resolved_box_shadow_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
BorderRadiiData PaintableBox::normalized_border_radii_data(Painting::RelativeToWidthOnly relative_to_width_only) const
|
BorderRadiiData PaintableBox::normalized_border_radii_data() const
|
||||||
{
|
{
|
||||||
return Painting::normalized_border_radii_data(layout_box(), absolute_border_box_rect(),
|
return Painting::normalized_border_radii_data(layout_box(), absolute_border_box_rect(),
|
||||||
computed_values().border_top_left_radius(),
|
computed_values().border_top_left_radius(),
|
||||||
computed_values().border_top_right_radius(),
|
computed_values().border_top_right_radius(),
|
||||||
computed_values().border_bottom_right_radius(),
|
computed_values().border_bottom_right_radius(),
|
||||||
computed_values().border_bottom_left_radius(),
|
computed_values().border_bottom_left_radius());
|
||||||
relative_to_width_only);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PaintableBox::before_children_paint(PaintContext& context, PaintPhase) const
|
void PaintableBox::before_children_paint(PaintContext& context, PaintPhase) const
|
||||||
|
|
|
@ -130,7 +130,7 @@ protected:
|
||||||
virtual Gfx::FloatRect compute_absolute_rect() const;
|
virtual Gfx::FloatRect compute_absolute_rect() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Painting::BorderRadiiData normalized_border_radii_data(Painting::RelativeToWidthOnly relative_to_width_only = Painting::RelativeToWidthOnly::No) const;
|
Painting::BorderRadiiData normalized_border_radii_data() const;
|
||||||
|
|
||||||
OwnPtr<Painting::StackingContext> m_stacking_context;
|
OwnPtr<Painting::StackingContext> m_stacking_context;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue