From 830632407f7649edf0ef94d52022762d7e26a5fa Mon Sep 17 00:00:00 2001 From: MacDue Date: Wed, 15 Jun 2022 21:15:29 +0100 Subject: LibWeb: Add BorderRadiusCornerClipper This is a helper class for clipping the corners off a element. This works in a similar way to how (outline) borders are painted. The steps are: 1. A small bitmap that fits only the corners is allocated 2. The corners are painted into the bitmap 3. The existing pixels (where the corners will be painted) are copied using the (inverse) corner bitmap as a mask (done before the element is painted) 4. The element is painted 5. The areas outside the corner radii are restored Like with the borders, this only requires allocation on the first paint. --- .../LibWeb/Painting/BorderRadiusCornerClipper.cpp | 103 +++++++++++++++++++++ .../LibWeb/Painting/BorderRadiusCornerClipper.h | 51 ++++++++++ 2 files changed, 154 insertions(+) create mode 100644 Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp create mode 100644 Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.h (limited to 'Userland/Libraries/LibWeb/Painting') 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) + { + } +}; + +} -- cgit v1.2.3