diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index b86615ba18..d896049765 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -289,6 +289,7 @@ set(SOURCES Page/Page.cpp Painting/BackgroundPainting.cpp Painting/BorderPainting.cpp + Painting/BorderRadiusCornerClipper.cpp Painting/ButtonPaintable.cpp Painting/CanvasPaintable.cpp Painting/CheckBoxPaintable.cpp diff --git a/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp b/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp new file mode 100644 index 0000000000..a87876bf93 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::Painting { + +ErrorOr BorderRadiusCornerClipper::create(Gfx::IntRect const& border_rect, BorderRadiiData const& border_radii) +{ + VERIFY(border_radii.has_any_radius()); + + auto top_left = border_radii.top_left.as_corner(); + auto top_right = border_radii.top_right.as_corner(); + auto bottom_right = border_radii.bottom_right.as_corner(); + auto bottom_left = border_radii.bottom_left.as_corner(); + + Gfx::IntSize corners_bitmap_size { + max( + top_left.horizontal_radius + top_right.horizontal_radius, + bottom_left.horizontal_radius + bottom_right.horizontal_radius), + max( + top_left.vertical_radius + bottom_left.vertical_radius, + top_right.vertical_radius + bottom_right.vertical_radius) + }; + + auto corner_bitmap = get_cached_corner_bitmap(corners_bitmap_size); + if (!corner_bitmap) + return Error::from_errno(ENOMEM); + + CornerData corner_data { + .corner_radii = { + .top_left = top_left, + .top_right = top_right, + .bottom_right = bottom_right, + .bottom_left = bottom_left }, + .page_locations = { .top_left = border_rect.top_left(), .top_right = border_rect.top_right().translated(-top_right.horizontal_radius + 1, 0), .bottom_right = border_rect.bottom_right().translated(-bottom_right.horizontal_radius + 1, -bottom_right.vertical_radius + 1), .bottom_left = border_rect.bottom_left().translated(0, -bottom_left.vertical_radius + 1) }, + .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 } }, + .corner_bitmap_size = corners_bitmap_size + }; + + return BorderRadiusCornerClipper { corner_data, corner_bitmap.release_nonnull() }; +} + +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 }; + Gfx::IntRect corner_rect { { 0, 0 }, m_data.corner_bitmap_size }; + corner_aa_painter.fill_rect_with_rounded_corners(corner_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 = [&](auto const& mask_src, auto 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(); + 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), m_data.page_locations.top_left); + 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), m_data.page_locations.top_right); + 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), m_data.page_locations.bottom_right); + 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), m_data.page_locations.bottom_left); + + 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, *m_corner_bitmap, m_data.corner_radii.top_left.as_rect().translated(m_data.bitmap_locations.top_left)); + if (m_data.corner_radii.top_right) + painter.blit(m_data.page_locations.top_right, *m_corner_bitmap, m_data.corner_radii.top_right.as_rect().translated(m_data.bitmap_locations.top_right)); + if (m_data.corner_radii.bottom_right) + painter.blit(m_data.page_locations.bottom_right, *m_corner_bitmap, m_data.corner_radii.bottom_right.as_rect().translated(m_data.bitmap_locations.bottom_right)); + if (m_data.corner_radii.bottom_left) + painter.blit(m_data.page_locations.bottom_left, *m_corner_bitmap, m_data.corner_radii.bottom_left.as_rect().translated(m_data.bitmap_locations.bottom_left)); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.h b/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.h new file mode 100644 index 0000000000..6186c679fc --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Painting { + +class BorderRadiusCornerClipper { +public: + static ErrorOr create(Gfx::IntRect const& border_rect, BorderRadiiData const& border_radii); + + void sample_under_corners(Gfx::Painter& page_painter); + void blit_corner_clipping(Gfx::Painter& page_painter); + +private: + using CornerRadius = Gfx::AntiAliasingPainter::CornerRadius; + struct CornerData { + struct CornerRadii { + CornerRadius top_left; + CornerRadius top_right; + CornerRadius bottom_right; + CornerRadius bottom_left; + } corner_radii; + struct CornerLocations { + Gfx::IntPoint top_left; + Gfx::IntPoint top_right; + Gfx::IntPoint bottom_right; + Gfx::IntPoint bottom_left; + }; + CornerLocations page_locations; + CornerLocations bitmap_locations; + Gfx::IntSize corner_bitmap_size; + } m_data; + + NonnullRefPtr m_corner_bitmap; + bool m_has_sampled { false }; + + BorderRadiusCornerClipper(CornerData corner_data, NonnullRefPtr corner_bitmap) + : m_data(move(corner_data)) + , m_corner_bitmap(corner_bitmap) + { + } +}; + +}