1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 11:58:12 +00:00
serenity/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp
MacDue 1c012f0a4a LibWeb: Remove "cached corner bitmap" and its use in the corner clipper
With the recording painter the actual painting operations are delayed,
so now if multiple corner clippers are constructed, and they use a
shared bitmap they can interfere with each other. The use of this shared
bitmap was somewhat questionable anyway, so this is not much of a loss.

This fixes the border-radius.html test page.
2023-10-21 23:16:17 +02:00

131 lines
6.9 KiB
C++

/*
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Bitmap.h>
#include <LibGfx/Painter.h>
#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
#include <LibWeb/Painting/PaintContext.h>
namespace Web::Painting {
ErrorOr<NonnullRefPtr<BorderRadiusCornerClipper>> BorderRadiusCornerClipper::create(CornerRadii const& corner_radii, DevicePixelRect const& border_rect, BorderRadiiData const& border_radii, CornerClip corner_clip)
{
VERIFY(border_radii.has_any_radius());
auto top_left = corner_radii.top_left;
auto top_right = corner_radii.top_right;
auto bottom_right = corner_radii.bottom_right;
auto bottom_left = corner_radii.bottom_left;
DevicePixelSize corners_bitmap_size {
max(
max(
top_left.horizontal_radius + top_right.horizontal_radius,
top_left.horizontal_radius + bottom_right.horizontal_radius),
max(
bottom_left.horizontal_radius + top_right.horizontal_radius,
bottom_left.horizontal_radius + bottom_right.horizontal_radius)),
max(
max(
top_left.vertical_radius + bottom_left.vertical_radius,
top_left.vertical_radius + bottom_right.vertical_radius),
max(
top_right.vertical_radius + bottom_left.vertical_radius,
top_right.vertical_radius + bottom_right.vertical_radius))
};
RefPtr<Gfx::Bitmap> corner_bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, corners_bitmap_size.to_type<int>()));
CornerData corner_data {
.corner_radii = corner_radii,
.page_locations = { .top_left = border_rect.top_left(), .top_right = border_rect.top_right().translated(-top_right.horizontal_radius, 0), .bottom_right = border_rect.bottom_right().translated(-bottom_right.horizontal_radius, -bottom_right.vertical_radius), .bottom_left = border_rect.bottom_left().translated(0, -bottom_left.vertical_radius) },
.bitmap_locations = { .top_left = { 0, 0 }, .top_right = { corners_bitmap_size.width() - top_right.horizontal_radius, 0 }, .bottom_right = { corners_bitmap_size.width() - bottom_right.horizontal_radius, corners_bitmap_size.height() - bottom_right.vertical_radius }, .bottom_left = { 0, corners_bitmap_size.height() - bottom_left.vertical_radius } },
};
return try_make_ref_counted<BorderRadiusCornerClipper>(corner_data, corner_bitmap.release_nonnull(), corner_clip, border_rect);
}
void BorderRadiusCornerClipper::sample_under_corners(Gfx::Painter& page_painter)
{
// Generate a mask for the corners:
Gfx::Painter corner_painter { *m_corner_bitmap };
Gfx::AntiAliasingPainter corner_aa_painter { corner_painter };
corner_aa_painter.fill_rect_with_rounded_corners(m_corner_bitmap->rect(), Color::NamedColor::Black,
m_data.corner_radii.top_left, m_data.corner_radii.top_right, m_data.corner_radii.bottom_right, m_data.corner_radii.bottom_left);
auto copy_page_masked = [&](Gfx::IntRect const& mask_src, Gfx::IntPoint const& page_location) {
for (int row = 0; row < mask_src.height(); ++row) {
for (int col = 0; col < mask_src.width(); ++col) {
auto corner_location = mask_src.location().translated(col, row);
auto mask_pixel = m_corner_bitmap->get_pixel(corner_location);
u8 mask_alpha = mask_pixel.alpha();
if (m_corner_clip == CornerClip::Outside)
mask_alpha = ~mask_pixel.alpha();
auto final_pixel = Color();
if (mask_alpha > 0) {
auto page_pixel = page_painter.get_pixel(page_location.translated(col, row));
if (page_pixel.has_value())
final_pixel = page_pixel.value().with_alpha(mask_alpha);
}
m_corner_bitmap->set_pixel(corner_location, final_pixel);
}
}
};
// Copy the pixels under the corner mask (using the alpha of the mask):
if (m_data.corner_radii.top_left)
copy_page_masked(m_data.corner_radii.top_left.as_rect().translated(m_data.bitmap_locations.top_left.to_type<int>()), m_data.page_locations.top_left.to_type<int>());
if (m_data.corner_radii.top_right)
copy_page_masked(m_data.corner_radii.top_right.as_rect().translated(m_data.bitmap_locations.top_right.to_type<int>()), m_data.page_locations.top_right.to_type<int>());
if (m_data.corner_radii.bottom_right)
copy_page_masked(m_data.corner_radii.bottom_right.as_rect().translated(m_data.bitmap_locations.bottom_right.to_type<int>()), m_data.page_locations.bottom_right.to_type<int>());
if (m_data.corner_radii.bottom_left)
copy_page_masked(m_data.corner_radii.bottom_left.as_rect().translated(m_data.bitmap_locations.bottom_left.to_type<int>()), m_data.page_locations.bottom_left.to_type<int>());
m_has_sampled = true;
}
void BorderRadiusCornerClipper::blit_corner_clipping(Gfx::Painter& painter)
{
VERIFY(m_has_sampled);
// Restore the corners:
if (m_data.corner_radii.top_left)
painter.blit(m_data.page_locations.top_left.to_type<int>(), *m_corner_bitmap, m_data.corner_radii.top_left.as_rect().translated(m_data.bitmap_locations.top_left.to_type<int>()));
if (m_data.corner_radii.top_right)
painter.blit(m_data.page_locations.top_right.to_type<int>(), *m_corner_bitmap, m_data.corner_radii.top_right.as_rect().translated(m_data.bitmap_locations.top_right.to_type<int>()));
if (m_data.corner_radii.bottom_right)
painter.blit(m_data.page_locations.bottom_right.to_type<int>(), *m_corner_bitmap, m_data.corner_radii.bottom_right.as_rect().translated(m_data.bitmap_locations.bottom_right.to_type<int>()));
if (m_data.corner_radii.bottom_left)
painter.blit(m_data.page_locations.bottom_left.to_type<int>(), *m_corner_bitmap, m_data.corner_radii.bottom_left.as_rect().translated(m_data.bitmap_locations.bottom_left.to_type<int>()));
}
ScopedCornerRadiusClip::ScopedCornerRadiusClip(PaintContext& context, DevicePixelRect const& border_rect, BorderRadiiData const& border_radii, CornerClip corner_clip)
: m_context(context)
{
if (border_radii.has_any_radius()) {
CornerRadii 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)
};
auto clipper = BorderRadiusCornerClipper::create(corner_radii, border_rect, border_radii, corner_clip);
if (!clipper.is_error()) {
m_corner_clipper = clipper.release_value();
m_context.painter().sample_under_corners(*m_corner_clipper);
}
}
}
ScopedCornerRadiusClip::~ScopedCornerRadiusClip()
{
if (m_corner_clipper) {
m_context.painter().blit_corner_clipping(*m_corner_clipper);
}
}
}