diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 100eb6c2f2f..755ebe0911a 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -469,6 +469,7 @@ set(SOURCES Page/Page.cpp Painting/AudioPaintable.cpp Painting/BackgroundPainting.cpp + Painting/BorderRadiiData.cpp Painting/BorderPainting.cpp Painting/BorderRadiusCornerClipper.cpp Painting/ButtonPaintable.cpp @@ -487,6 +488,7 @@ set(SOURCES Painting/PaintableBox.cpp Painting/ProgressPaintable.cpp Painting/RadioButtonPaintable.cpp + Painting/RecordingPainter.cpp Painting/SVGGeometryPaintable.cpp Painting/SVGGraphicsPaintable.cpp Painting/SVGPaintable.cpp diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.cpp index 95fed74e47d..25ef7f37cc7 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace Web::CSS { diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 999c2cfc77d..23511170794 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -22,6 +22,10 @@ class ResourceLoader; class XMLDocumentBuilder; } +namespace Web::Painting { +class RecordingPainter; +} + namespace Web::ARIA { class AriaData; class ARIAMixin; diff --git a/Userland/Libraries/LibWeb/Layout/Box.cpp b/Userland/Libraries/LibWeb/Layout/Box.cpp index 245b83b6404..bb991a08cd5 100644 --- a/Userland/Libraries/LibWeb/Layout/Box.cpp +++ b/Userland/Libraries/LibWeb/Layout/Box.cpp @@ -5,7 +5,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include #include diff --git a/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp b/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp index 7205c70107f..df053968130 100644 --- a/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/CanvasBox.cpp @@ -4,7 +4,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include diff --git a/Userland/Libraries/LibWeb/Painting/AudioPaintable.cpp b/Userland/Libraries/LibWeb/Painting/AudioPaintable.cpp index d7890613277..dd6adf29992 100644 --- a/Userland/Libraries/LibWeb/Painting/AudioPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/AudioPaintable.cpp @@ -43,21 +43,17 @@ void AudioPaintable::paint(PaintContext& context, PaintPhase phase) const if (!is_visible()) return; - // FIXME: This should be done at a different level. - if (is_out_of_view(context)) - return; - Base::paint(context, phase); if (phase != PaintPhase::Foreground) return; - Gfx::PainterStateSaver saver { context.painter() }; + RecordingPainterStateSaver saver { context.painter() }; auto audio_rect = context.rounded_device_rect(absolute_rect()); context.painter().add_clip_rect(audio_rect.to_type()); - ScopedCornerRadiusClip corner_clip { context, context.painter(), audio_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; + ScopedCornerRadiusClip corner_clip { context, audio_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; auto const& audio_element = layout_box().dom_node(); auto mouse_position = MediaPaintable::mouse_position(context, audio_element); diff --git a/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp b/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp index d5aa8231b6f..761a2228178 100644 --- a/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp @@ -7,7 +7,6 @@ */ #include -#include #include #include #include @@ -15,6 +14,7 @@ #include #include #include +#include namespace Web::Painting { @@ -76,9 +76,13 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet } } - Gfx::AntiAliasingPainter aa_painter { painter }; - aa_painter.fill_rect_with_rounded_corners(context.rounded_device_rect(color_box.rect).to_type(), - 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)); + context.painter().fill_rect_with_rounded_corners( + context.rounded_device_rect(color_box.rect).to_type(), + 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)); if (!has_paintable_layers) return; @@ -107,7 +111,7 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet for (auto& layer : background_layers->in_reverse()) { if (!layer_is_paintable(layer)) continue; - Gfx::PainterStateSaver state { painter }; + RecordingPainterStateSaver state { painter }; // Clip auto clip_box = get_box(layer.clip); @@ -115,7 +119,7 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet CSSPixelRect const& css_clip_rect = clip_box.rect; auto clip_rect = context.rounded_device_rect(css_clip_rect); painter.add_clip_rect(clip_rect.to_type()); - ScopedCornerRadiusClip corner_clip { context, painter, clip_rect, clip_box.radii }; + ScopedCornerRadiusClip corner_clip { context, clip_rect, clip_box.radii }; if (layer.clip == CSS::BackgroundBox::BorderBox) { // Shrink the effective clip rect if to account for the bits the borders will definitely paint over @@ -326,7 +330,7 @@ void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMet while (image_x < css_clip_rect.right()) { image_rect.set_x(image_x); auto image_device_rect = context.rounded_device_rect(image_rect); - if (image_device_rect != last_image_device_rect && !context.would_be_fully_clipped_by_painter(image_device_rect)) + if (image_device_rect != last_image_device_rect) image.paint(context, image_device_rect, image_rendering); last_image_device_rect = image_device_rect; if (!repeat_x) diff --git a/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp b/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp index d5a1ddc3a6d..92776fc3046 100644 --- a/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp @@ -200,8 +200,7 @@ void paint_border(PaintContext& context, BorderEdge edge, DevicePixelRect const& break; } if (border_style == CSS::LineStyle::Dotted) { - Gfx::AntiAliasingPainter aa_painter { context.painter() }; - aa_painter.draw_line(p1.to_type(), p2.to_type(), color, device_pixel_width.value(), gfx_line_style); + context.painter().draw_line(p1.to_type(), p2.to_type(), color, device_pixel_width.value(), gfx_line_style); return; } context.painter().draw_line(p1.to_type(), p2.to_type(), color, device_pixel_width.value(), gfx_line_style); @@ -245,9 +244,8 @@ void paint_border(PaintContext& context, BorderEdge edge, DevicePixelRect const& // If joined borders have the same color, combine them to draw together. if (ready_to_draw) { - Gfx::AntiAliasingPainter aa_painter { context.painter() }; path.close_all_subpaths(); - aa_painter.fill_path(path, color, Gfx::Painter::WindingRule::EvenOdd); + context.painter().fill_path({ .path = path, .color = color, .winding_rule = Gfx::Painter::WindingRule::EvenOdd }); path.clear(); } }; diff --git a/Userland/Libraries/LibWeb/Painting/BorderPainting.h b/Userland/Libraries/LibWeb/Painting/BorderPainting.h index f0c4e4cf06b..ec263af3d0d 100644 --- a/Userland/Libraries/LibWeb/Painting/BorderPainting.h +++ b/Userland/Libraries/LibWeb/Painting/BorderPainting.h @@ -10,61 +10,12 @@ #include #include #include -#include +#include +#include +#include namespace Web::Painting { -struct BorderRadiusData { - CSSPixels horizontal_radius { 0 }; - CSSPixels vertical_radius { 0 }; - - Gfx::AntiAliasingPainter::CornerRadius as_corner(PaintContext& context) const - { - return Gfx::AntiAliasingPainter::CornerRadius { - context.floored_device_pixels(horizontal_radius).value(), - context.floored_device_pixels(vertical_radius).value() - }; - } - - inline operator bool() const - { - return horizontal_radius > 0 && vertical_radius > 0; - } - - inline void shrink(CSSPixels horizontal, CSSPixels vertical) - { - if (horizontal_radius != 0) - horizontal_radius = max(CSSPixels(0), horizontal_radius - horizontal); - if (vertical_radius != 0) - vertical_radius = max(CSSPixels(0), vertical_radius - vertical); - } -}; - -struct BorderRadiiData { - BorderRadiusData top_left; - BorderRadiusData top_right; - BorderRadiusData bottom_right; - BorderRadiusData bottom_left; - - inline bool has_any_radius() const - { - return top_left || top_right || bottom_right || bottom_left; - } - - inline void shrink(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left) - { - top_left.shrink(left, top); - top_right.shrink(right, top); - bottom_right.shrink(right, bottom); - bottom_left.shrink(left, bottom); - } - - inline void inflate(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left) - { - shrink(-top, -right, -bottom, -left); - } -}; - BorderRadiiData normalized_border_radii_data(Layout::Node const&, CSSPixelRect const&, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius); enum class BorderEdge { @@ -73,12 +24,6 @@ enum class BorderEdge { Bottom, Left, }; -struct BordersData { - CSS::BorderData top; - CSS::BorderData right; - CSS::BorderData bottom; - CSS::BorderData left; -}; // Returns OptionalNone if there is no outline to paint. Optional borders_data_for_outline(Layout::Node const&, Color outline_color, CSS::OutlineStyle outline_style, CSSPixels outline_width); diff --git a/Userland/Libraries/LibWeb/Painting/BorderRadiiData.cpp b/Userland/Libraries/LibWeb/Painting/BorderRadiiData.cpp new file mode 100644 index 00000000000..b05a873148f --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/BorderRadiiData.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2021-2023, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::Painting { + +Gfx::AntiAliasingPainter::CornerRadius BorderRadiusData::as_corner(PaintContext& context) const +{ + return Gfx::AntiAliasingPainter::CornerRadius { + context.floored_device_pixels(horizontal_radius).value(), + context.floored_device_pixels(vertical_radius).value() + }; +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h b/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h new file mode 100644 index 00000000000..8c0e327d60a --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2021-2023, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::Painting { + +struct BorderRadiusData { + CSSPixels horizontal_radius { 0 }; + CSSPixels vertical_radius { 0 }; + + Gfx::AntiAliasingPainter::CornerRadius as_corner(PaintContext& context) const; + + inline operator bool() const + { + return horizontal_radius > 0 && vertical_radius > 0; + } + + inline void shrink(CSSPixels horizontal, CSSPixels vertical) + { + if (horizontal_radius != 0) + horizontal_radius = max(CSSPixels(0), horizontal_radius - horizontal); + if (vertical_radius != 0) + vertical_radius = max(CSSPixels(0), vertical_radius - vertical); + } +}; + +struct BorderRadiiData { + BorderRadiusData top_left; + BorderRadiusData top_right; + BorderRadiusData bottom_right; + BorderRadiusData bottom_left; + + inline bool has_any_radius() const + { + return top_left || top_right || bottom_right || bottom_left; + } + + inline void shrink(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left) + { + top_left.shrink(left, top); + top_right.shrink(right, top); + bottom_right.shrink(right, bottom); + bottom_left.shrink(left, bottom); + } + + inline void inflate(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left) + { + shrink(-top, -right, -bottom, -left); + } +}; + +using CornerRadius = Gfx::AntiAliasingPainter::CornerRadius; + +struct CornerRadii { + CornerRadius top_left; + CornerRadius top_right; + CornerRadius bottom_right; + CornerRadius bottom_left; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp b/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp index 5ce47713a89..2d94995d840 100644 --- a/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp +++ b/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp @@ -7,17 +7,18 @@ #include #include #include +#include namespace Web::Painting { -ErrorOr BorderRadiusCornerClipper::create(PaintContext& context, DevicePixelRect const& border_rect, BorderRadiiData const& border_radii, CornerClip corner_clip, UseCachedBitmap use_cached_bitmap) +ErrorOr> BorderRadiusCornerClipper::create(CornerRadii const& corner_radii, DevicePixelRect const& border_rect, BorderRadiiData const& border_radii, CornerClip corner_clip, UseCachedBitmap use_cached_bitmap) { VERIFY(border_radii.has_any_radius()); - auto top_left = border_radii.top_left.as_corner(context); - auto top_right = border_radii.top_right.as_corner(context); - auto bottom_right = border_radii.bottom_right.as_corner(context); - auto bottom_left = border_radii.bottom_left.as_corner(context); + 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( @@ -46,17 +47,13 @@ ErrorOr BorderRadiusCornerClipper::create(PaintContex } CornerData corner_data { - .corner_radii = { - .top_left = top_left, - .top_right = top_right, - .bottom_right = bottom_right, - .bottom_left = bottom_left }, + .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 } }, .corner_bitmap_size = corners_bitmap_size }; - return BorderRadiusCornerClipper { corner_data, corner_bitmap.release_nonnull(), corner_clip }; + return try_make_ref_counted(corner_data, corner_bitmap.release_nonnull(), corner_clip); } void BorderRadiusCornerClipper::sample_under_corners(Gfx::Painter& page_painter) @@ -115,4 +112,29 @@ void BorderRadiusCornerClipper::blit_corner_clipping(Gfx::Painter& painter) painter.blit(m_data.page_locations.bottom_left.to_type(), *m_corner_bitmap, m_data.corner_radii.bottom_left.as_rect().translated(m_data.bitmap_locations.bottom_left.to_type())); } +ScopedCornerRadiusClip::ScopedCornerRadiusClip(PaintContext& context, DevicePixelRect const& border_rect, BorderRadiiData const& border_radii, CornerClip corner_clip, BorderRadiusCornerClipper::UseCachedBitmap use_cached_bitmap) + : 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, use_cached_bitmap); + 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); + } +} + } diff --git a/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.h b/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.h index 00a090379c2..9068a6ba73c 100644 --- a/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.h +++ b/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.h @@ -16,27 +16,20 @@ enum class CornerClip { Inside }; -class BorderRadiusCornerClipper { +class BorderRadiusCornerClipper : public RefCounted { public: enum class UseCachedBitmap { Yes, No }; - static ErrorOr create(PaintContext&, DevicePixelRect const& border_rect, BorderRadiiData const& border_radii, CornerClip corner_clip = CornerClip::Outside, UseCachedBitmap use_cached_bitmap = UseCachedBitmap::Yes); + static ErrorOr> create(CornerRadii const&, DevicePixelRect const& border_rect, BorderRadiiData const& border_radii, CornerClip corner_clip = CornerClip::Outside, UseCachedBitmap use_cached_bitmap = UseCachedBitmap::Yes); 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; + CornerRadii corner_radii; struct CornerLocations { DevicePixelPoint top_left; DevicePixelPoint top_right; @@ -48,44 +41,30 @@ private: DevicePixelSize corner_bitmap_size; } m_data; - NonnullRefPtr m_corner_bitmap; - bool m_has_sampled { false }; - CornerClip m_corner_clip { false }; - BorderRadiusCornerClipper(CornerData corner_data, NonnullRefPtr corner_bitmap, CornerClip corner_clip) : m_data(move(corner_data)) , m_corner_bitmap(corner_bitmap) , m_corner_clip(corner_clip) { } + +private: + NonnullRefPtr m_corner_bitmap; + bool m_has_sampled { false }; + CornerClip m_corner_clip { false }; }; struct ScopedCornerRadiusClip { - ScopedCornerRadiusClip(PaintContext& context, Gfx::Painter& painter, DevicePixelRect const& border_rect, BorderRadiiData const& border_radii, CornerClip corner_clip = CornerClip::Outside, BorderRadiusCornerClipper::UseCachedBitmap use_cached_bitmap = BorderRadiusCornerClipper::UseCachedBitmap::Yes) - : m_painter(painter) - { - if (border_radii.has_any_radius()) { - auto clipper = BorderRadiusCornerClipper::create(context, border_rect, border_radii, corner_clip, use_cached_bitmap); - if (!clipper.is_error()) { - m_corner_clipper = clipper.release_value(); - m_corner_clipper->sample_under_corners(m_painter); - } - } - } + ScopedCornerRadiusClip(PaintContext& context, DevicePixelRect const& border_rect, BorderRadiiData const& border_radii, CornerClip corner_clip = CornerClip::Outside, BorderRadiusCornerClipper::UseCachedBitmap use_cached_bitmap = BorderRadiusCornerClipper::UseCachedBitmap::Yes); - ~ScopedCornerRadiusClip() - { - if (m_corner_clipper.has_value()) { - m_corner_clipper->blit_corner_clipping(m_painter); - } - } + ~ScopedCornerRadiusClip(); AK_MAKE_NONMOVABLE(ScopedCornerRadiusClip); AK_MAKE_NONCOPYABLE(ScopedCornerRadiusClip); private: - Gfx::Painter& m_painter; - Optional m_corner_clipper; + PaintContext& m_context; + RefPtr m_corner_clipper; }; } diff --git a/Userland/Libraries/LibWeb/Painting/BordersData.h b/Userland/Libraries/LibWeb/Painting/BordersData.h new file mode 100644 index 00000000000..84923cb4bbe --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/BordersData.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2021-2023, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +namespace Web::Painting { + +struct BordersData { + CSS::BorderData top; + CSS::BorderData right; + CSS::BorderData bottom; + CSS::BorderData left; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/CanvasPaintable.cpp b/Userland/Libraries/LibWeb/Painting/CanvasPaintable.cpp index de08c1c5b89..c29ad45ead5 100644 --- a/Userland/Libraries/LibWeb/Painting/CanvasPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/CanvasPaintable.cpp @@ -32,11 +32,7 @@ void CanvasPaintable::paint(PaintContext& context, PaintPhase phase) const if (phase == PaintPhase::Foreground) { auto canvas_rect = context.rounded_device_rect(absolute_rect()); - ScopedCornerRadiusClip corner_clip { context, context.painter(), canvas_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; - - // FIXME: This should be done at a different level. - if (is_out_of_view(context)) - return; + ScopedCornerRadiusClip corner_clip { context, canvas_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; if (layout_box().dom_node().bitmap()) { // FIXME: Remove this const_cast. diff --git a/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp b/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp index bfaa793cfc0..6c5efb9cfc1 100644 --- a/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp @@ -103,7 +103,6 @@ void CheckBoxPaintable::paint(PaintContext& context, PaintPhase phase) const auto const& checkbox = static_cast(layout_box().dom_node()); bool enabled = layout_box().dom_node().enabled(); - Gfx::AntiAliasingPainter painter { context.painter() }; auto checkbox_rect = context.enclosing_device_rect(absolute_rect()).to_type(); auto checkbox_radius = checkbox_rect.width() / 5; @@ -135,7 +134,7 @@ void CheckBoxPaintable::paint(PaintContext& context, PaintPhase phase) const float smoothness = 1.0f / (max(checkbox_rect.width(), checkbox_rect.height()) / 2); if (checkbox.checked() && !checkbox.indeterminate()) { auto background_color = enabled ? input_colors.accent : input_colors.mid_gray; - painter.fill_rect_with_rounded_corners(checkbox_rect, modify_color(background_color), checkbox_radius); + context.painter().fill_rect_with_rounded_corners(checkbox_rect, modify_color(background_color), checkbox_radius); auto tick_color = increase_contrast(input_colors.base, background_color); if (!enabled) tick_color = shade(tick_color, 0.5f); @@ -143,8 +142,8 @@ void CheckBoxPaintable::paint(PaintContext& context, PaintPhase phase) const } else { auto background_color = input_colors.background_color(enabled); auto border_thickness = max(1, checkbox_rect.width() / 10); - painter.fill_rect_with_rounded_corners(checkbox_rect, modify_color(input_colors.border_color(enabled)), checkbox_radius); - painter.fill_rect_with_rounded_corners(checkbox_rect.shrunken(border_thickness, border_thickness, border_thickness, border_thickness), + context.painter().fill_rect_with_rounded_corners(checkbox_rect, modify_color(input_colors.border_color(enabled)), checkbox_radius); + context.painter().fill_rect_with_rounded_corners(checkbox_rect.shrunken(border_thickness, border_thickness, border_thickness, border_thickness), background_color, max(0, checkbox_radius - border_thickness)); if (checkbox.indeterminate()) { auto dash_color = increase_contrast(input_colors.dark_gray, background_color); diff --git a/Userland/Libraries/LibWeb/Painting/FilterPainting.cpp b/Userland/Libraries/LibWeb/Painting/FilterPainting.cpp index 07912b5f478..13fb415d1ba 100644 --- a/Userland/Libraries/LibWeb/Painting/FilterPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/FilterPainting.cpp @@ -100,40 +100,10 @@ void apply_filter_list(Gfx::Bitmap& target_bitmap, ReadonlySpan(), Gfx::BitmapFormat::BGRA8888, actual_region); - if (actual_region.is_empty()) - return; - if (maybe_backdrop_bitmap.is_error()) { - dbgln("Failed get region bitmap for backdrop-filter"); - return; - } - auto backdrop_bitmap = maybe_backdrop_bitmap.release_value(); - // 2. Apply the backdrop-filter’s filter operations to the entire contents of T'. - apply_filter_list(*backdrop_bitmap, backdrop_filter.filters); - - // FIXME: 3. If element B has any transforms (between B and the Backdrop Root), apply the inverse of those transforms to the contents of T’. - - // 4. Apply a clip to the contents of T’, using the border box of element B, including border-radius if specified. Note that the children of B are not considered for the sizing or location of this clip. - ScopedCornerRadiusClip corner_clipper { context, context.painter(), backdrop_region, border_radii_data }; - - // FIXME: 5. Draw all of element B, including its background, border, and any children elements, into T’. - - // FXIME: 6. If element B has any transforms, effects, or clips, apply those to T’. - - // 7. Composite the contents of T’ into element B’s parent, using source-over compositing. - context.painter().blit(actual_region.location(), *backdrop_bitmap, backdrop_bitmap->rect()); + ScopedCornerRadiusClip corner_clipper { context, backdrop_region, border_radii_data }; + context.painter().apply_backdrop_filter(backdrop_region, border_radii_data, backdrop_filter); } } diff --git a/Userland/Libraries/LibWeb/Painting/GradientData.h b/Userland/Libraries/LibWeb/Painting/GradientData.h new file mode 100644 index 00000000000..9cd6140ad8c --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/GradientData.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::Painting { + +using ColorStopList = Vector; + +struct ColorStopData { + ColorStopList list; + Optional repeat_length; +}; + +struct LinearGradientData { + float gradient_angle; + ColorStopData color_stops; +}; + +struct ConicGradientData { + float start_angle; + ColorStopData color_stops; +}; + +struct RadialGradientData { + ColorStopData color_stops; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp b/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp index ed51481bad1..06867a07308 100644 --- a/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -148,17 +147,17 @@ RadialGradientData resolve_radial_gradient_data(Layout::NodeWithStyleAndBoxModel void paint_linear_gradient(PaintContext& context, DevicePixelRect const& gradient_rect, LinearGradientData const& data) { - context.painter().fill_rect_with_linear_gradient(gradient_rect.to_type(), data.color_stops.list, data.gradient_angle, data.color_stops.repeat_length); + context.painter().fill_rect_with_linear_gradient(gradient_rect.to_type(), data); } void paint_conic_gradient(PaintContext& context, DevicePixelRect const& gradient_rect, ConicGradientData const& data, DevicePixelPoint position) { - context.painter().fill_rect_with_conic_gradient(gradient_rect.to_type(), data.color_stops.list, position.to_type(), data.start_angle, data.color_stops.repeat_length); + context.painter().fill_rect_with_conic_gradient(gradient_rect.to_type(), data, position.to_type()); } void paint_radial_gradient(PaintContext& context, DevicePixelRect const& gradient_rect, RadialGradientData const& data, DevicePixelPoint center, DevicePixelSize size) { - context.painter().fill_rect_with_radial_gradient(gradient_rect.to_type(), data.color_stops.list, center.to_type(), size.to_type(), data.color_stops.repeat_length); + context.painter().fill_rect_with_radial_gradient(gradient_rect.to_type(), data, center, size); } } diff --git a/Userland/Libraries/LibWeb/Painting/GradientPainting.h b/Userland/Libraries/LibWeb/Painting/GradientPainting.h index 3ca5ea4c19b..c4e7c55017d 100644 --- a/Userland/Libraries/LibWeb/Painting/GradientPainting.h +++ b/Userland/Libraries/LibWeb/Painting/GradientPainting.h @@ -11,31 +11,11 @@ #include #include #include +#include #include namespace Web::Painting { -using ColorStopList = Vector; - -struct ColorStopData { - ColorStopList list; - Optional repeat_length; -}; - -struct LinearGradientData { - float gradient_angle; - ColorStopData color_stops; -}; - -struct ConicGradientData { - float start_angle; - ColorStopData color_stops; -}; - -struct RadialGradientData { - ColorStopData color_stops; -}; - LinearGradientData resolve_linear_gradient_data(Layout::NodeWithStyleAndBoxModelMetrics const&, CSSPixelSize, CSS::LinearGradientStyleValue const&); ConicGradientData resolve_conic_gradient_data(Layout::NodeWithStyleAndBoxModelMetrics const&, CSS::ConicGradientStyleValue const&); RadialGradientData resolve_radial_gradient_data(Layout::NodeWithStyleAndBoxModelMetrics const&, CSSPixelSize, CSS::RadialGradientStyleValue const&); diff --git a/Userland/Libraries/LibWeb/Painting/ImagePaintable.cpp b/Userland/Libraries/LibWeb/Painting/ImagePaintable.cpp index aba446e7a32..c3b90aebf55 100644 --- a/Userland/Libraries/LibWeb/Painting/ImagePaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ImagePaintable.cpp @@ -45,10 +45,6 @@ void ImagePaintable::paint(PaintContext& context, PaintPhase phase) const if (!is_visible()) return; - // FIXME: This should be done at a different level. - if (is_out_of_view(context)) - return; - PaintableBox::paint(context, phase); if (phase == PaintPhase::Foreground) { @@ -57,13 +53,13 @@ void ImagePaintable::paint(PaintContext& context, PaintPhase phase) const auto& image_element = verify_cast(*dom_node()); auto enclosing_rect = context.enclosing_device_rect(absolute_rect()).to_type(); context.painter().set_font(Platform::FontPlugin::the().default_font()); - Gfx::StylePainter::paint_frame(context.painter(), enclosing_rect, context.palette(), Gfx::FrameStyle::SunkenContainer); + context.painter().paint_frame(enclosing_rect, context.palette(), Gfx::FrameStyle::SunkenContainer); auto alt = image_element.alt(); if (alt.is_empty()) alt = image_element.src(); context.painter().draw_text(enclosing_rect, alt, Gfx::TextAlignment::Center, computed_values().color(), Gfx::TextElision::Right); } else if (auto bitmap = layout_box().image_provider().current_image_bitmap(image_rect.size().to_type())) { - ScopedCornerRadiusClip corner_clip { context, context.painter(), image_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; + ScopedCornerRadiusClip corner_clip { context, image_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; auto image_int_rect = image_rect.to_type(); auto bitmap_rect = bitmap->rect(); auto scaling_mode = to_gfx_scaling_mode(computed_values().image_rendering(), bitmap_rect, image_int_rect); diff --git a/Userland/Libraries/LibWeb/Painting/LinearGradientData.h b/Userland/Libraries/LibWeb/Painting/LinearGradientData.h new file mode 100644 index 00000000000..9cd6140ad8c --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/LinearGradientData.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::Painting { + +using ColorStopList = Vector; + +struct ColorStopData { + ColorStopList list; + Optional repeat_length; +}; + +struct LinearGradientData { + float gradient_angle; + ColorStopData color_stops; +}; + +struct ConicGradientData { + float start_angle; + ColorStopData color_stops; +}; + +struct RadialGradientData { + ColorStopData color_stops; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/MarkerPaintable.cpp b/Userland/Libraries/LibWeb/Painting/MarkerPaintable.cpp index cd0f9a22c6b..423208b28c6 100644 --- a/Userland/Libraries/LibWeb/Painting/MarkerPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/MarkerPaintable.cpp @@ -66,17 +66,15 @@ void MarkerPaintable::paint(PaintContext& context, PaintPhase phase) const auto color = computed_values().color(); - Gfx::AntiAliasingPainter aa_painter { context.painter() }; - switch (layout_box().list_style_type()) { case CSS::ListStyleType::Square: context.painter().fill_rect(device_marker_rect.to_type(), color); break; case CSS::ListStyleType::Circle: - aa_painter.draw_ellipse(device_marker_rect.to_type(), color, 1); + context.painter().draw_ellipse(device_marker_rect.to_type(), color, 1); break; case CSS::ListStyleType::Disc: - aa_painter.fill_ellipse(device_marker_rect.to_type(), color); + context.painter().fill_ellipse(device_marker_rect.to_type(), color); break; case CSS::ListStyleType::DisclosureClosed: { // https://drafts.csswg.org/css-counter-styles-3/#disclosure-closed @@ -89,7 +87,7 @@ void MarkerPaintable::paint(PaintContext& context, PaintPhase phase) const path.line_to({ left + sin_60_deg * (right - left), (top + bottom) / 2 }); path.line_to({ left, bottom }); path.close(); - aa_painter.fill_path(path, color); + context.painter().fill_path({ .path = path, .color = color, .winding_rule = Gfx::Painter::WindingRule::EvenOdd }); break; } case CSS::ListStyleType::DisclosureOpen: { @@ -103,7 +101,7 @@ void MarkerPaintable::paint(PaintContext& context, PaintPhase phase) const path.line_to({ right, top }); path.line_to({ (left + right) / 2, top + sin_60_deg * (bottom - top) }); path.close(); - aa_painter.fill_path(path, color); + context.painter().fill_path({ .path = path, .color = color, .winding_rule = Gfx::Painter::WindingRule::EvenOdd }); break; } case CSS::ListStyleType::Decimal: diff --git a/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp b/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp index b1f430ac0ae..35985fa5a24 100644 --- a/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp @@ -44,15 +44,18 @@ Optional MediaPaintable::mouse_position(PaintContext& context, return {}; } -void MediaPaintable::fill_triangle(Gfx::Painter& painter, Gfx::IntPoint location, Array coordinates, Color color) +void MediaPaintable::fill_triangle(RecordingPainter& painter, Gfx::IntPoint location, Array coordinates, Color color) { - Gfx::AntiAliasingPainter aa_painter { painter }; Gfx::Path path; path.move_to((coordinates[0] + location).to_type()); path.line_to((coordinates[1] + location).to_type()); path.line_to((coordinates[2] + location).to_type()); path.close(); - aa_painter.fill_path(path, color, Gfx::Painter::WindingRule::EvenOdd); + painter.fill_path({ + .path = path, + .color = color, + .winding_rule = Gfx::Painter::WindingRule::EvenOdd, + }); } void MediaPaintable::paint_media_controls(PaintContext& context, HTML::HTMLMediaElement const& media_element, DevicePixelRect media_rect, Optional const& mouse_position) const @@ -214,7 +217,6 @@ void MediaPaintable::paint_control_bar_speaker(PaintContext& context, HTML::HTML auto speaker_button_is_hovered = rect_is_hovered(media_element, components.speaker_button_rect, mouse_position); auto speaker_button_color = control_button_color(speaker_button_is_hovered); - Gfx::AntiAliasingPainter painter { context.painter() }; Gfx::Path path; path.move_to(device_point(0, 4)); @@ -225,18 +227,18 @@ void MediaPaintable::paint_control_bar_speaker(PaintContext& context, HTML::HTML path.line_to(device_point(0, 11)); path.line_to(device_point(0, 4)); path.close(); - painter.fill_path(path, speaker_button_color, Gfx::Painter::WindingRule::EvenOdd); + context.painter().fill_path({ .path = path, .color = speaker_button_color, .winding_rule = Gfx::Painter::WindingRule::EvenOdd }); path.clear(); path.move_to(device_point(13, 3)); path.quadratic_bezier_curve_to(device_point(16, 7.5), device_point(13, 12)); path.move_to(device_point(14, 0)); path.quadratic_bezier_curve_to(device_point(20, 7.5), device_point(14, 15)); - painter.stroke_path(path, speaker_button_color, 1); + context.painter().stroke_path({ .path = path, .color = speaker_button_color, .thickness = 1 }); if (media_element.muted()) { - painter.draw_line(device_point(0, 0), device_point(20, 15), Color::Red, 2); - painter.draw_line(device_point(0, 15), device_point(20, 0), Color::Red, 2); + context.painter().draw_line(device_point(0, 0).to_type(), device_point(20, 15).to_type(), Color::Red, 2); + context.painter().draw_line(device_point(0, 15).to_type(), device_point(20, 0).to_type(), Color::Red, 2); } } @@ -248,15 +250,13 @@ void MediaPaintable::paint_control_bar_volume(PaintContext& context, HTML::HTMLM auto volume_position = static_cast(static_cast(components.volume_scrub_rect.width())) * media_element.volume(); auto volume_button_offset_x = static_cast(round(volume_position)); - Gfx::AntiAliasingPainter painter { context.painter() }; - auto volume_lower_rect = components.volume_scrub_rect; volume_lower_rect.set_width(volume_button_offset_x); - painter.fill_rect_with_rounded_corners(volume_lower_rect.to_type(), control_highlight_color.lightened(), 4); + context.painter().fill_rect_with_rounded_corners(volume_lower_rect.to_type(), control_highlight_color.lightened(), 4); auto volume_higher_rect = components.volume_scrub_rect; volume_higher_rect.take_from_left(volume_button_offset_x); - painter.fill_rect_with_rounded_corners(volume_higher_rect.to_type(), Color::Black, 4); + context.painter().fill_rect_with_rounded_corners(volume_higher_rect.to_type(), Color::Black, 4); auto volume_button_rect = components.volume_scrub_rect; volume_button_rect.shrink(components.volume_scrub_rect.width() - components.volume_button_size, components.volume_scrub_rect.height() - components.volume_button_size); @@ -264,7 +264,7 @@ void MediaPaintable::paint_control_bar_volume(PaintContext& context, HTML::HTMLM auto volume_is_hovered = rect_is_hovered(media_element, components.volume_rect, mouse_position, HTML::HTMLMediaElement::MouseTrackingComponent::Volume); auto volume_color = control_button_color(volume_is_hovered); - painter.fill_ellipse(volume_button_rect.to_type(), volume_color); + context.painter().fill_ellipse(volume_button_rect.to_type(), volume_color); } MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mousedown(Badge, CSSPixelPoint position, unsigned button, unsigned) diff --git a/Userland/Libraries/LibWeb/Painting/MediaPaintable.h b/Userland/Libraries/LibWeb/Painting/MediaPaintable.h index 7552b92cb5d..eecabf57192 100644 --- a/Userland/Libraries/LibWeb/Painting/MediaPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/MediaPaintable.h @@ -20,7 +20,7 @@ protected: explicit MediaPaintable(Layout::ReplacedBox const&); static Optional mouse_position(PaintContext&, HTML::HTMLMediaElement const&); - static void fill_triangle(Gfx::Painter& painter, Gfx::IntPoint location, Array coordinates, Color color); + static void fill_triangle(RecordingPainter& painter, Gfx::IntPoint location, Array coordinates, Color color); void paint_media_controls(PaintContext&, HTML::HTMLMediaElement const&, DevicePixelRect media_rect, Optional const& mouse_position) const; diff --git a/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp b/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp index 1324de9e548..0d243537307 100644 --- a/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/NestedBrowsingContextPaintable.cpp @@ -39,7 +39,7 @@ void NestedBrowsingContextPaintable::paint(PaintContext& context, PaintPhase pha if (phase == PaintPhase::Foreground) { auto absolute_rect = this->absolute_rect(); auto clip_rect = context.rounded_device_rect(absolute_rect); - ScopedCornerRadiusClip corner_clip { context, context.painter(), clip_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; + ScopedCornerRadiusClip corner_clip { context, clip_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; auto* hosted_document = layout_box().dom_node().content_document_without_origin_check(); if (!hosted_document) diff --git a/Userland/Libraries/LibWeb/Painting/PaintContext.cpp b/Userland/Libraries/LibWeb/Painting/PaintContext.cpp index 69c6607a0c9..04f1b8cc9d0 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintContext.cpp @@ -5,12 +5,11 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include namespace Web { -PaintContext::PaintContext(Gfx::Painter& painter, Palette const& palette, double device_pixels_per_css_pixel) +PaintContext::PaintContext(Painting::RecordingPainter& painter, Palette const& palette, double device_pixels_per_css_pixel) : m_painter(painter) , m_palette(palette) , m_device_pixels_per_css_pixel(device_pixels_per_css_pixel) @@ -123,9 +122,4 @@ CSSPixelRect PaintContext::scale_to_css_rect(DevicePixelRect rect) const }; } -bool PaintContext::would_be_fully_clipped_by_painter(DevicePixelRect rect) const -{ - return !painter().clip_rect().intersects(rect.to_type().translated(painter().translation())); -} - } diff --git a/Userland/Libraries/LibWeb/Painting/PaintContext.h b/Userland/Libraries/LibWeb/Painting/PaintContext.h index fd427d0c89e..cde8a8ec9b3 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintContext.h +++ b/Userland/Libraries/LibWeb/Painting/PaintContext.h @@ -11,15 +11,16 @@ #include #include #include +#include #include namespace Web { class PaintContext { public: - PaintContext(Gfx::Painter& painter, Palette const& palette, double device_pixels_per_css_pixel); + PaintContext(Painting::RecordingPainter& painter, Palette const& palette, double device_pixels_per_css_pixel); - Gfx::Painter& painter() const { return m_painter; } + Painting::RecordingPainter& painter() const { return m_painter; } Palette const& palette() const { return m_palette; } bool should_show_line_box_borders() const { return m_should_show_line_box_borders; } @@ -29,8 +30,6 @@ public: void set_device_viewport_rect(DevicePixelRect const& rect) { m_device_viewport_rect = rect; } CSSPixelRect css_viewport_rect() const; - [[nodiscard]] bool would_be_fully_clipped_by_painter(DevicePixelRect) const; - bool has_focus() const { return m_focus; } void set_has_focus(bool focus) { m_focus = focus; } @@ -58,7 +57,7 @@ public: CSSPixelSize scale_to_css_size(DevicePixelSize) const; CSSPixelRect scale_to_css_rect(DevicePixelRect) const; - PaintContext clone(Gfx::Painter& painter) const + PaintContext clone(Painting::RecordingPainter& painter) const { auto clone = PaintContext(painter, m_palette, m_device_pixels_per_css_pixel); clone.m_device_viewport_rect = m_device_viewport_rect; @@ -73,7 +72,7 @@ public: void translate_scroll_offset_by(CSSPixelPoint offset) { m_scroll_offset.translate_by(offset); } private: - Gfx::Painter& m_painter; + Painting::RecordingPainter& m_painter; Palette m_palette; double m_device_pixels_per_css_pixel { 0 }; DevicePixelRect m_device_viewport_rect; diff --git a/Userland/Libraries/LibWeb/Painting/PaintOuterBoxShadowParams.h b/Userland/Libraries/LibWeb/Painting/PaintOuterBoxShadowParams.h new file mode 100644 index 00000000000..a11e4d102be --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/PaintOuterBoxShadowParams.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Painting { + +struct PaintOuterBoxShadowParams { + RecordingPainter& painter; + CSSPixelRect content_rect; + BorderRadiiData border_radii; + ShadowData box_shadow_data; + CornerRadii corner_radii; + DevicePixels offset_x; + DevicePixels offset_y; + DevicePixels blur_radius; + DevicePixels spread_distance; + DevicePixelRect device_content_rect; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index f5017692c42..535080ded5d 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -50,11 +50,6 @@ void PaintableBox::invalidate_stacking_context() m_stacking_context = nullptr; } -bool PaintableBox::is_out_of_view(PaintContext& context) const -{ - return context.would_be_fully_clipped_by_painter(context.enclosing_device_rect(absolute_paint_rect())); -} - PaintableWithLines::PaintableWithLines(Layout::BlockContainer const& layout_box) : PaintableBox(layout_box) { @@ -482,14 +477,20 @@ void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase ph if (!clip_rect->is_empty() && overflow_y == CSS::Overflow::Hidden && overflow_x == CSS::Overflow::Hidden) { auto border_radii_data = normalized_border_radii_data(ShrinkRadiiForBorders::Yes); + CornerRadii corner_radii { + .top_left = border_radii_data.top_left.as_corner(context), + .top_right = border_radii_data.top_right.as_corner(context), + .bottom_right = border_radii_data.bottom_right.as_corner(context), + .bottom_left = border_radii_data.bottom_left.as_corner(context) + }; if (border_radii_data.has_any_radius()) { - auto corner_clipper = BorderRadiusCornerClipper::create(context, context.rounded_device_rect(*clip_rect), border_radii_data, CornerClip::Outside, BorderRadiusCornerClipper::UseCachedBitmap::No); + auto corner_clipper = BorderRadiusCornerClipper::create(corner_radii, context.rounded_device_rect(*clip_rect), border_radii_data, CornerClip::Outside, BorderRadiusCornerClipper::UseCachedBitmap::No); if (corner_clipper.is_error()) { dbgln("Failed to create overflow border-radius corner clipper: {}", corner_clipper.error()); return; } m_overflow_corner_radius_clipper = corner_clipper.release_value(); - m_overflow_corner_radius_clipper->sample_under_corners(context.painter()); + context.painter().sample_under_corners(*m_overflow_corner_radius_clipper); } } } @@ -504,9 +505,9 @@ void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase ph context.painter().restore(); m_clipping_overflow = false; } - if (m_overflow_corner_radius_clipper.has_value()) { - m_overflow_corner_radius_clipper->blit_corner_clipping(context.painter()); - m_overflow_corner_radius_clipper = {}; + if (m_overflow_corner_radius_clipper) { + context.painter().blit_corner_clipping(*m_overflow_corner_radius_clipper); + m_overflow_corner_radius_clipper = nullptr; } } @@ -544,8 +545,9 @@ static void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const context.painter().draw_rect(cursor_device_rect, text_node.computed_values().color()); } -static void paint_text_decoration(PaintContext& context, Gfx::Painter& painter, Layout::Node const& text_node, Layout::LineBoxFragment const& fragment) +static void paint_text_decoration(PaintContext& context, Layout::Node const& text_node, Layout::LineBoxFragment const& fragment) { + auto& painter = context.painter(); auto& font = fragment.layout_node().font(); auto fragment_box = fragment.absolute_rect(); CSSPixels glyph_height = CSSPixels::nearest_value_for(font.pixel_size()); @@ -643,17 +645,17 @@ static void paint_text_fragment(PaintContext& context, Layout::TextNode const& t auto& scaled_font = fragment.layout_node().scaled_font(context); - painter.draw_text_run(baseline_start.to_type(), view, scaled_font, text_node.computed_values().color()); + painter.draw_text_run(baseline_start.to_type(), view, scaled_font, text_node.computed_values().color(), fragment_absolute_device_rect.to_type()); auto selection_rect = context.enclosing_device_rect(fragment.selection_rect(text_node.font())).to_type(); if (!selection_rect.is_empty()) { painter.fill_rect(selection_rect, CSS::SystemColor::highlight()); - Gfx::PainterStateSaver saver(painter); + RecordingPainterStateSaver saver(painter); painter.add_clip_rect(selection_rect); - painter.draw_text_run(baseline_start.to_type(), view, scaled_font, CSS::SystemColor::highlight_text()); + painter.draw_text_run(baseline_start.to_type(), view, scaled_font, CSS::SystemColor::highlight_text(), fragment_absolute_device_rect.to_type()); } - paint_text_decoration(context, painter, text_node, fragment); + paint_text_decoration(context, text_node, fragment); paint_cursor_if_needed(context, text_node, fragment); } } @@ -669,7 +671,7 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const return; bool should_clip_overflow = computed_values().overflow_x() != CSS::Overflow::Visible && computed_values().overflow_y() != CSS::Overflow::Visible; - Optional corner_clipper; + RefPtr corner_clipper; if (should_clip_overflow) { context.painter().save(); @@ -680,11 +682,17 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const context.painter().translate(-scroll_offset.to_type()); auto border_radii = normalized_border_radii_data(ShrinkRadiiForBorders::Yes); + 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) + }; if (border_radii.has_any_radius()) { - auto clipper = BorderRadiusCornerClipper::create(context, clip_box, border_radii); + auto clipper = BorderRadiusCornerClipper::create(corner_radii, clip_box, border_radii); if (!clipper.is_error()) { corner_clipper = clipper.release_value(); - corner_clipper->sample_under_corners(context.painter()); + context.painter().sample_under_corners(*corner_clipper); } } } @@ -722,8 +730,6 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const for (auto& fragment : line_box.fragments()) { auto fragment_absolute_rect = fragment.absolute_rect(); auto fragment_absolute_device_rect = context.enclosing_device_rect(fragment_absolute_rect); - if (context.would_be_fully_clipped_by_painter(fragment_absolute_device_rect)) - continue; if (context.should_show_line_box_borders()) { context.painter().draw_rect(fragment_absolute_device_rect.to_type(), Color::Green); context.painter().draw_line( @@ -737,8 +743,8 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const if (should_clip_overflow) { context.painter().restore(); - if (corner_clipper.has_value()) - corner_clipper->blit_corner_clipping(context.painter()); + if (corner_clipper) + context.painter().blit_corner_clipping(*corner_clipper); } } diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index 1aa699d653e..63ef8195b98 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -28,7 +28,8 @@ public: [[nodiscard]] bool is_visible() const; virtual Optional get_masking_area() const { return {}; } - virtual void apply_mask(PaintContext&, Gfx::Bitmap&, CSSPixelRect const&) const {}; + virtual Optional get_mask_type() const { return {}; } + virtual RefPtr calculate_mask(PaintContext&, CSSPixelRect const&) const { return {}; } Layout::Box& layout_box() { return static_cast(Paintable::layout_node()); } Layout::Box const& layout_box() const { return static_cast(Paintable::layout_node()); } @@ -144,8 +145,6 @@ public: void invalidate_stacking_context(); - bool is_out_of_view(PaintContext&) const; - enum class ConflictingElementKind { Cell, Row, @@ -220,7 +219,7 @@ private: Optional mutable m_clip_rect; mutable bool m_clipping_overflow { false }; - Optional mutable m_overflow_corner_radius_clipper; + RefPtr mutable m_overflow_corner_radius_clipper; Optional m_override_borders_data; Optional m_table_cell_coordinates; diff --git a/Userland/Libraries/LibWeb/Painting/ProgressPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ProgressPaintable.cpp index 63b2e5719b6..050abd60b36 100644 --- a/Userland/Libraries/LibWeb/Painting/ProgressPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ProgressPaintable.cpp @@ -34,9 +34,14 @@ void ProgressPaintable::paint(PaintContext& context, PaintPhase phase) const auto min_frame_thickness = context.rounded_device_pixels(3); auto frame_thickness = min(min(progress_rect.width(), progress_rect.height()) / 6, min_frame_thickness); - Gfx::StylePainter::paint_progressbar(context.painter(), progress_rect.shrunken(frame_thickness, frame_thickness).to_type(), context.palette(), 0, round_to(layout_box().dom_node().max()), round_to(layout_box().dom_node().value()), ""sv); - - Gfx::StylePainter::paint_frame(context.painter(), progress_rect.to_type(), context.palette(), Gfx::FrameStyle::RaisedBox); + context.painter().paint_progressbar( + progress_rect.to_type(), + progress_rect.shrunken(frame_thickness, frame_thickness).to_type(), + context.palette(), + 0, + round_to(layout_box().dom_node().max()), + round_to(layout_box().dom_node().value()), + ""sv); } } diff --git a/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp b/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp index 08d8f97d9d3..f93f5138f5c 100644 --- a/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp @@ -37,12 +37,10 @@ void RadioButtonPaintable::paint(PaintContext& context, PaintPhase phase) const if (phase != PaintPhase::Foreground) return; - Gfx::AntiAliasingPainter painter { context.painter() }; - auto draw_circle = [&](auto const& rect, Color color) { // Note: Doing this is a bit more forgiving than draw_circle() which will round to the nearset even radius. // This will fudge it (which works better here). - painter.fill_rect_with_rounded_corners(rect, color, rect.width() / 2); + context.painter().fill_rect_with_rounded_corners(rect, color, rect.width() / 2); }; auto shrink_all = [&](auto const& rect, int amount) { diff --git a/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp b/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp new file mode 100644 index 00000000000..0c49bd70f3e --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp @@ -0,0 +1,883 @@ +/* + * Copyright (c) 2023, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Web::Painting { + +struct CommandExecutionState { + struct StackingContext { + Gfx::Painter painter; + Gfx::IntRect destination; + float opacity; + }; + + [[nodiscard]] Gfx::Painter const& painter() const { return stacking_contexts.last().painter; } + [[nodiscard]] Gfx::Painter& painter() { return stacking_contexts.last().painter; } + + [[nodiscard]] bool would_be_fully_clipped_by_painter(Gfx::IntRect rect) const + { + return !painter().clip_rect().intersects(rect.translated(painter().translation())); + } + + Vector stacking_contexts; +}; + +CommandResult ClearRect::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + + state.painter().clear_rect(rect, color); + return CommandResult::Continue; +} + +CommandResult FillRectWithRoundedCorners::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + ; + + auto& painter = state.painter(); + + Gfx::AntiAliasingPainter aa_painter(painter); + if (aa_translation.has_value()) + aa_painter.translate(*aa_translation); + aa_painter.fill_rect_with_rounded_corners( + rect, + color, + top_left_radius, + top_right_radius, + bottom_right_radius, + bottom_left_radius); + return CommandResult::Continue; +} + +CommandResult DrawText::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + ; + auto& painter = state.painter(); + if (font.has_value()) { + painter.draw_text(rect, raw_text, *font, alignment, color, elision, wrapping); + } else { + painter.draw_text(rect, raw_text, alignment, color, elision, wrapping); + } + return CommandResult::Continue; +} + +CommandResult DrawTextRun::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + ; + auto& painter = state.painter(); + painter.draw_text_run(baseline_start, Utf8View(string), font, color); + return CommandResult::Continue; +} + +CommandResult FillPathUsingColor::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + Gfx::AntiAliasingPainter aa_painter(painter); + if (aa_translation.has_value()) + aa_painter.translate(*aa_translation); + aa_painter.fill_path(path, color, winding_rule); + return CommandResult::Continue; +} + +CommandResult FillPathUsingPaintStyle::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + Gfx::AntiAliasingPainter aa_painter(painter); + if (aa_translation.has_value()) + aa_painter.translate(*aa_translation); + aa_painter.fill_path(path, paint_style, opacity, winding_rule); + return CommandResult::Continue; +} + +CommandResult StrokePathUsingColor::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + Gfx::AntiAliasingPainter aa_painter(painter); + if (aa_translation.has_value()) + aa_painter.translate(*aa_translation); + aa_painter.stroke_path(path, color, thickness); + return CommandResult::Continue; +} + +CommandResult StrokePathUsingPaintStyle::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + Gfx::AntiAliasingPainter aa_painter(painter); + if (aa_translation.has_value()) + aa_painter.translate(*aa_translation); + aa_painter.stroke_path(path, paint_style, thickness, opacity); + return CommandResult::Continue; +} + +CommandResult FillRect::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + auto& painter = state.painter(); + painter.fill_rect(rect, color); + return CommandResult::Continue; +} + +CommandResult DrawScaledBitmap::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(dst_rect)) + return CommandResult::Continue; + auto& painter = state.painter(); + painter.draw_scaled_bitmap(dst_rect, bitmap, src_rect, opacity, scaling_mode); + return CommandResult::Continue; +} + +CommandResult Translate::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + painter.translate(translation_delta); + return CommandResult::Continue; +} + +CommandResult SaveState::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + painter.save(); + return CommandResult::Continue; +} + +CommandResult RestoreState::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + painter.restore(); + return CommandResult::Continue; +} + +CommandResult AddClipRect::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + painter.add_clip_rect(rect); + return CommandResult::Continue; +} + +CommandResult ClearClipRect::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + painter.clear_clip_rect(); + return CommandResult::Continue; +} + +CommandResult SetFont::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + painter.set_font(font); + return CommandResult::Continue; +} + +CommandResult PushStackingContext::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + if (has_fixed_position) { + painter.translate(-painter.translation()); + } + if (semitransparent_or_has_non_identity_transform) { + auto destination_rect = transformed_destination_rect.to_rounded(); + + // FIXME: We should find a way to scale the paintable, rather than paint into a separate bitmap, + // then scale it. This snippet now copies the background at the destination, then scales it down/up + // to the size of the source (which could add some artefacts, though just scaling the bitmap already does that). + // We need to copy the background at the destination because a bunch of our rendering effects now rely on + // being able to sample the painter (see border radii, shadows, filters, etc). + Gfx::FloatPoint destination_clipped_fixup {}; + auto try_get_scaled_destination_bitmap = [&]() -> ErrorOr> { + Gfx::IntRect actual_destination_rect; + auto bitmap = TRY(painter.get_region_bitmap(destination_rect, Gfx::BitmapFormat::BGRA8888, actual_destination_rect)); + // get_region_bitmap() may clip to a smaller region if the requested rect goes outside the painter, so we need to account for that. + destination_clipped_fixup = Gfx::FloatPoint { destination_rect.location() - actual_destination_rect.location() }; + destination_rect = actual_destination_rect; + if (source_rect.size() != transformed_destination_rect.size()) { + auto sx = static_cast(source_rect.width()) / transformed_destination_rect.width(); + auto sy = static_cast(source_rect.height()) / transformed_destination_rect.height(); + bitmap = TRY(bitmap->scaled(sx, sy)); + destination_clipped_fixup.scale_by(sx, sy); + } + return bitmap; + }; + + auto bitmap_or_error = try_get_scaled_destination_bitmap(); + if (bitmap_or_error.is_error()) { + // NOTE: If the creation of the bitmap fails, we need to skip all painting commands that belong to this stacking context. + // We don't interrupt the execution of painting commands because get_region_bitmap() returns an error if the requested + // region is outside of the viewport (mmap fails to allocate a zero-size region), which means we can safely proceed + // with execution of commands outside of this stacking context. + // FIXME: Change the get_region_bitmap() API to return ErrorOr> and exit the execution of commands here + // if we run out of memory. + return CommandResult::SkipStackingContext; + } + auto bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors(); + + Gfx::Painter stacking_context_painter(bitmap); + + stacking_context_painter.translate(painter_location.to_type() + destination_clipped_fixup.to_type()); + + state.stacking_contexts.append(CommandExecutionState::StackingContext { + .painter = stacking_context_painter, + .destination = destination_rect, + .opacity = opacity, + }); + } else { + state.painter().save(); + } + + return CommandResult::Continue; +} + +CommandResult PopStackingContext::execute(CommandExecutionState& state) const +{ + if (semitransparent_or_has_non_identity_transform) { + auto stacking_context = state.stacking_contexts.take_last(); + auto bitmap = stacking_context.painter.target(); + auto destination_rect = stacking_context.destination; + + if (destination_rect.size() == bitmap->size()) { + state.painter().blit(destination_rect.location(), *bitmap, bitmap->rect(), stacking_context.opacity); + } else { + state.painter().draw_scaled_bitmap(destination_rect, *bitmap, bitmap->rect(), stacking_context.opacity, scaling_mode); + } + } else { + state.painter().restore(); + } + + return CommandResult::Continue; +} + +CommandResult PushStackingContextWithMask::execute(CommandExecutionState& state) const +{ + auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, paint_rect.size().to_type()); + if (bitmap_or_error.is_error()) + return CommandResult::Continue; + ; + auto bitmap = bitmap_or_error.release_value(); + + Gfx::Painter stacking_context_painter(bitmap); + + stacking_context_painter.translate(-paint_rect.location().to_type()); + + state.stacking_contexts.append(CommandExecutionState::StackingContext { + .painter = stacking_context_painter, + .destination = {}, + .opacity = 1, + }); + + return CommandResult::Continue; +} + +CommandResult PopStackingContextWithMask::execute(CommandExecutionState& state) const +{ + auto stacking_context = state.stacking_contexts.take_last(); + auto bitmap = stacking_context.painter.target(); + if (mask_bitmap) + bitmap->apply_mask(*mask_bitmap, mask_kind); + state.painter().blit(paint_rect.location().to_type(), *bitmap, bitmap->rect(), opacity); + return CommandResult::Continue; +} + +CommandResult PaintLinearGradient::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(gradient_rect)) + return CommandResult::Continue; + auto const& data = linear_gradient_data; + state.painter().fill_rect_with_linear_gradient( + gradient_rect, data.color_stops.list, + data.gradient_angle, data.color_stops.repeat_length); + return CommandResult::Continue; +} + +CommandResult PaintRadialGradient::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + ; + auto& painter = state.painter(); + painter.fill_rect_with_radial_gradient(rect, radial_gradient_data.color_stops.list, center, size, radial_gradient_data.color_stops.repeat_length); + return CommandResult::Continue; +} + +CommandResult PaintConicGradient::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + ; + auto& painter = state.painter(); + painter.fill_rect_with_conic_gradient(rect, conic_gradient_data.color_stops.list, position, conic_gradient_data.start_angle, conic_gradient_data.color_stops.repeat_length); + return CommandResult::Continue; +} + +CommandResult PaintOuterBoxShadow::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + paint_outer_box_shadow(painter, outer_box_shadow_params); + return CommandResult::Continue; +} + +CommandResult PaintInnerBoxShadow::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + paint_inner_box_shadow(painter, outer_box_shadow_params); + return CommandResult::Continue; +} + +CommandResult PaintTextShadow::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(text_rect.to_type())) + return CommandResult::Continue; + ; + + // FIXME: Figure out the maximum bitmap size for all shadows and then allocate it once and reuse it? + auto maybe_shadow_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, bounding_rect.size().to_type()); + if (maybe_shadow_bitmap.is_error()) { + dbgln("Unable to allocate temporary bitmap {} for text-shadow rendering: {}", bounding_rect.size(), maybe_shadow_bitmap.error()); + return CommandResult::Continue; + ; + } + auto shadow_bitmap = maybe_shadow_bitmap.release_value(); + + Gfx::Painter shadow_painter { *shadow_bitmap }; + // FIXME: "Spread" the shadow somehow. + DevicePixelPoint baseline_start(text_rect.x(), text_rect.y() + fragment_baseline); + shadow_painter.draw_text_run(baseline_start.to_type(), Utf8View(text), font, color); + + // Blur + Gfx::StackBlurFilter filter(*shadow_bitmap); + filter.process_rgba(blur_radius.value(), color); + + auto& painter = state.painter(); + painter.blit(draw_location.to_type(), *shadow_bitmap, bounding_rect.to_type()); + return CommandResult::Continue; +} + +CommandResult DrawEllipse::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + ; + auto& painter = state.painter(); + Gfx::AntiAliasingPainter aa_painter(painter); + aa_painter.draw_ellipse(rect, color, thickness); + return CommandResult::Continue; +} + +CommandResult FillElipse::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + ; + auto& painter = state.painter(); + Gfx::AntiAliasingPainter aa_painter(painter); + aa_painter.fill_ellipse(rect, color, blend_mode); + return CommandResult::Continue; +} + +CommandResult DrawLine::execute(CommandExecutionState& state) const +{ + if (style == Gfx::Painter::LineStyle::Dotted) { + Gfx::AntiAliasingPainter aa_painter(state.painter()); + aa_painter.draw_line(from, to, color, thickness, style, alternate_color); + } else { + state.painter().draw_line(from, to, color, thickness, style, alternate_color); + } + return CommandResult::Continue; +} + +CommandResult DrawSignedDistanceField::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + ; + auto& painter = state.painter(); + painter.draw_signed_distance_field(rect, color, sdf, smoothing); + return CommandResult::Continue; +} + +CommandResult PaintProgressbar::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + Gfx::StylePainter::paint_progressbar(painter, progress_rect, palette, min, max, value, text); + Gfx::StylePainter::paint_frame(painter, frame_rect, palette, Gfx::FrameStyle::RaisedBox); + return CommandResult::Continue; +} + +CommandResult PaintFrame::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + Gfx::StylePainter::paint_frame(painter, rect, palette, style); + return CommandResult::Continue; +} + +CommandResult ApplyBackdropFilter::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + + // This performs the backdrop filter operation: https://drafts.fxtf.org/filter-effects-2/#backdrop-filter-operation + + // Note: The region bitmap can be smaller than the backdrop_region if it's at the edge of canvas. + // Note: This is in DevicePixels, but we use an IntRect because `get_region_bitmap()` below writes to it. + + // FIXME: Go through the steps to find the "Backdrop Root Image" + // https://drafts.fxtf.org/filter-effects-2/#BackdropRoot + + // 1. Copy the Backdrop Root Image into a temporary buffer, such as a raster image. Call this buffer T’. + Gfx::IntRect actual_region {}; + auto maybe_backdrop_bitmap = painter.get_region_bitmap(backdrop_region, Gfx::BitmapFormat::BGRA8888, actual_region); + if (actual_region.is_empty()) + return CommandResult::Continue; + ; + if (maybe_backdrop_bitmap.is_error()) { + dbgln("Failed get region bitmap for backdrop-filter"); + return CommandResult::Continue; + } + auto backdrop_bitmap = maybe_backdrop_bitmap.release_value(); + + // 2. Apply the backdrop-filter’s filter operations to the entire contents of T'. + apply_filter_list(*backdrop_bitmap, backdrop_filter.filters); + + // FIXME: 3. If element B has any transforms (between B and the Backdrop Root), apply the inverse of those transforms to the contents of T’. + + // 4. Apply a clip to the contents of T’, using the border box of element B, including border-radius if specified. Note that the children of B are not considered for the sizing or location of this clip. + // FIXME: 5. Draw all of element B, including its background, border, and any children elements, into T’. + // FXIME: 6. If element B has any transforms, effects, or clips, apply those to T’. + + // 7. Composite the contents of T’ into element B’s parent, using source-over compositing. + painter.blit(actual_region.location(), *backdrop_bitmap, backdrop_bitmap->rect()); + return CommandResult::Continue; +} + +CommandResult DrawRect::execute(CommandExecutionState& state) const +{ + if (state.would_be_fully_clipped_by_painter(rect)) + return CommandResult::Continue; + ; + auto& painter = state.painter(); + painter.draw_rect(rect, color, rough); + return CommandResult::Continue; +} + +CommandResult DrawTriangleWave::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + painter.draw_triangle_wave(p1, p2, color, amplitude, thickness); + return CommandResult::Continue; +} + +CommandResult SampleUnderCorners::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + corner_clipper->sample_under_corners(painter); + return CommandResult::Continue; +} + +CommandResult BlitCornerClipping::execute(CommandExecutionState& state) const +{ + auto& painter = state.painter(); + corner_clipper->blit_corner_clipping(painter); + return CommandResult::Continue; +} + +void RecordingPainter::sample_under_corners(NonnullRefPtr corner_clipper) +{ + push_command(SampleUnderCorners { corner_clipper }); +} + +void RecordingPainter::blit_corner_clipping(NonnullRefPtr corner_clipper) +{ + push_command(BlitCornerClipping { corner_clipper }); +} + +void RecordingPainter::clear_rect(Gfx::IntRect const& rect, Color color) +{ + push_command(ClearRect { + .rect = rect, + .color = color, + }); +} + +void RecordingPainter::fill_rect(Gfx::IntRect const& rect, Color color) +{ + push_command(FillRect { + .rect = rect, + .color = color, + }); +} + +void RecordingPainter::fill_path(FillPathUsingColorParams params) +{ + push_command(FillPathUsingColor { + .path = params.path, + .color = params.color, + .winding_rule = params.winding_rule, + .aa_translation = params.translation, + }); +} + +void RecordingPainter::fill_path(FillPathUsingPaintStyleParams params) +{ + push_command(FillPathUsingPaintStyle { + .path = params.path, + .paint_style = params.paint_style, + .winding_rule = params.winding_rule, + .opacity = params.opacity, + .aa_translation = params.translation, + }); +} + +void RecordingPainter::stroke_path(StrokePathUsingColorParams params) +{ + push_command(StrokePathUsingColor { + .path = params.path, + .color = params.color, + .thickness = params.thickness, + .aa_translation = params.translation, + }); +} + +void RecordingPainter::stroke_path(StrokePathUsingPaintStyleParams params) +{ + push_command(StrokePathUsingPaintStyle { + .path = params.path, + .paint_style = params.paint_style, + .thickness = params.thickness, + .aa_translation = params.translation, + }); +} + +void RecordingPainter::draw_ellipse(Gfx::IntRect const& a_rect, Color color, int thickness) +{ + push_command(DrawEllipse { + .rect = a_rect, + .color = color, + .thickness = thickness, + }); +} + +void RecordingPainter::fill_ellipse(Gfx::IntRect const& a_rect, Color color, Gfx::AntiAliasingPainter::BlendMode blend_mode) +{ + push_command(FillElipse { + .rect = a_rect, + .color = color, + .blend_mode = blend_mode, + }); +} + +void RecordingPainter::fill_rect_with_linear_gradient(Gfx::IntRect const& gradient_rect, LinearGradientData const& data) +{ + push_command(PaintLinearGradient { + .gradient_rect = gradient_rect, + .linear_gradient_data = data, + }); +} + +void RecordingPainter::fill_rect_with_conic_gradient(Gfx::IntRect const& rect, ConicGradientData const& data, Gfx::IntPoint const& position) +{ + push_command(PaintConicGradient { + .rect = rect, + .conic_gradient_data = data, + .position = position }); +} + +void RecordingPainter::fill_rect_with_radial_gradient(Gfx::IntRect const& rect, RadialGradientData const& data, DevicePixelPoint center, DevicePixelSize size) +{ + push_command(PaintRadialGradient { + .rect = rect, + .radial_gradient_data = data, + .center = center.to_type(), + .size = size.to_type() }); +} + +void RecordingPainter::draw_rect(Gfx::IntRect const& rect, Color color, bool rough) +{ + push_command(DrawRect { + .rect = rect, + .color = color, + .rough = rough }); +} + +void RecordingPainter::draw_scaled_bitmap(Gfx::IntRect const& dst_rect, Gfx::Bitmap const& bitmap, Gfx::IntRect const& src_rect, float opacity, Gfx::Painter::ScalingMode scaling_mode) +{ + push_command(DrawScaledBitmap { + .dst_rect = dst_rect, + .bitmap = bitmap, + .src_rect = src_rect, + .opacity = opacity, + .scaling_mode = scaling_mode, + }); +} + +void RecordingPainter::draw_line(Gfx::IntPoint from, Gfx::IntPoint to, Color color, int thickness, Gfx::Painter::LineStyle style, Color alternate_color) +{ + push_command(DrawLine { + .color = color, + .from = from, + .to = to, + .thickness = thickness, + .style = style, + .alternate_color = alternate_color, + }); +} + +void RecordingPainter::draw_text(Gfx::IntRect const& rect, StringView raw_text, Gfx::TextAlignment alignment, Color color, Gfx::TextElision elision, Gfx::TextWrapping wrapping) +{ + push_command(DrawText { + .rect = rect, + .raw_text = String::from_utf8(raw_text).release_value_but_fixme_should_propagate_errors(), + .alignment = alignment, + .color = color, + .elision = elision, + .wrapping = wrapping, + }); +} + +void RecordingPainter::draw_text(Gfx::IntRect const& rect, StringView raw_text, Gfx::Font const& font, Gfx::TextAlignment alignment, Color color, Gfx::TextElision elision, Gfx::TextWrapping wrapping) +{ + push_command(DrawText { + .rect = rect, + .raw_text = String::from_utf8(raw_text).release_value_but_fixme_should_propagate_errors(), + .alignment = alignment, + .color = color, + .elision = elision, + .wrapping = wrapping, + .font = font, + }); +} + +void RecordingPainter::draw_signed_distance_field(Gfx::IntRect const& dst_rect, Color color, Gfx::GrayscaleBitmap const& sdf, float smoothing) +{ + push_command(DrawSignedDistanceField { + .rect = dst_rect, + .color = color, + .sdf = sdf, + .smoothing = smoothing, + }); +} + +void RecordingPainter::draw_text_run(Gfx::IntPoint baseline_start, Utf8View string, Gfx::Font const& font, Color color, Gfx::IntRect const& rect) +{ + push_command(DrawTextRun { + .color = color, + .baseline_start = baseline_start, + .string = String::from_utf8(string.as_string()).release_value_but_fixme_should_propagate_errors(), + .font = font, + .rect = rect, + }); +} + +void RecordingPainter::add_clip_rect(Gfx::IntRect const& rect) +{ + push_command(AddClipRect { + .rect = rect, + }); +} + +void RecordingPainter::clear_clip_rect() +{ + push_command(ClearClipRect {}); +} + +void RecordingPainter::translate(int dx, int dy) +{ + push_command(Translate { + .translation_delta = Gfx::IntPoint { dx, dy }, + }); +} + +void RecordingPainter::translate(Gfx::IntPoint delta) +{ + push_command(Translate { + .translation_delta = delta, + }); +} + +void RecordingPainter::set_font(Gfx::Font const& font) +{ + push_command(SetFont { .font = font }); +} + +void RecordingPainter::save() +{ + push_command(SaveState {}); +} + +void RecordingPainter::restore() +{ + push_command(RestoreState {}); +} + +void RecordingPainter::push_stacking_context(PushStackingContextParams params) +{ + push_command(PushStackingContext { + .semitransparent_or_has_non_identity_transform = params.semitransparent_or_has_non_identity_transform, + .has_fixed_position = params.has_fixed_position, + .opacity = params.opacity, + .source_rect = params.source_rect, + .transformed_destination_rect = params.transformed_destination_rect, + .painter_location = params.painter_location, + }); +} + +void RecordingPainter::pop_stacking_context(PopStackingContextParams params) +{ + push_command(PopStackingContext { + .semitransparent_or_has_non_identity_transform = params.semitransparent_or_has_non_identity_transform, + .scaling_mode = params.scaling_mode, + }); +} + +void RecordingPainter::paint_progressbar(Gfx::IntRect frame_rect, Gfx::IntRect progress_rect, Palette palette, int min, int max, int value, StringView text) +{ + push_command(PaintProgressbar { + .frame_rect = frame_rect, + .progress_rect = progress_rect, + .palette = palette, + .min = min, + .max = max, + .value = value, + .text = text, + }); +} + +void RecordingPainter::paint_frame(Gfx::IntRect rect, Palette palette, Gfx::FrameStyle style) +{ + push_command(PaintFrame { rect, palette, style }); +} + +void RecordingPainter::apply_backdrop_filter(DevicePixelRect const& backdrop_region, BorderRadiiData const& border_radii_data, CSS::ResolvedBackdropFilter const& backdrop_filter) +{ + push_command(ApplyBackdropFilter { + .backdrop_region = backdrop_region.to_type(), + .border_radii_data = border_radii_data, + .backdrop_filter = backdrop_filter, + }); +} + +void RecordingPainter::paint_outer_box_shadow_params(PaintOuterBoxShadowParams params) +{ + push_command(PaintOuterBoxShadow { + .outer_box_shadow_params = params, + }); +} + +void RecordingPainter::paint_inner_box_shadow_params(PaintOuterBoxShadowParams params) +{ + push_command(PaintInnerBoxShadow { + .outer_box_shadow_params = params, + }); +} + +void RecordingPainter::paint_text_shadow(DevicePixels blur_radius, DevicePixelRect bounding_rect, DevicePixelRect text_rect, Utf8View text, Gfx::Font const& font, Color color, DevicePixels fragment_baseline, DevicePixelPoint draw_location) +{ + push_command(PaintTextShadow { + .blur_radius = blur_radius, + .bounding_rect = bounding_rect, + .text_rect = text_rect, + .text = String::from_utf8(text.as_string()).release_value_but_fixme_should_propagate_errors(), + .font = font, + .color = color, + .fragment_baseline = fragment_baseline, + .draw_location = draw_location }); +} + +void RecordingPainter::fill_rect_with_rounded_corners(Gfx::IntRect const& rect, Color color, Gfx::AntiAliasingPainter::CornerRadius top_left_radius, Gfx::AntiAliasingPainter::CornerRadius top_right_radius, Gfx::AntiAliasingPainter::CornerRadius bottom_right_radius, Gfx::AntiAliasingPainter::CornerRadius bottom_left_radius) +{ + push_command(FillRectWithRoundedCorners { + .rect = rect, + .color = color, + .top_left_radius = top_left_radius, + .top_right_radius = top_right_radius, + .bottom_left_radius = bottom_left_radius, + .bottom_right_radius = bottom_right_radius, + }); +} + +void RecordingPainter::fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect, Color color, int radius) +{ + fill_rect_with_rounded_corners(a_rect, color, radius, radius, radius, radius); +} + +void RecordingPainter::fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect, Color color, int top_left_radius, int top_right_radius, int bottom_right_radius, int bottom_left_radius) +{ + fill_rect_with_rounded_corners(a_rect, color, + { top_left_radius, top_left_radius }, + { top_right_radius, top_right_radius }, + { bottom_right_radius, bottom_right_radius }, + { bottom_left_radius, bottom_left_radius }); +} + +void RecordingPainter::push_stacking_context_with_mask(DevicePixelRect paint_rect) +{ + push_command(PushStackingContextWithMask { .paint_rect = paint_rect }); +} + +void RecordingPainter::pop_stacking_context_with_mask(RefPtr mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, DevicePixelRect paint_rect, float opacity) +{ + push_command(PopStackingContextWithMask { + .paint_rect = paint_rect, + .mask_bitmap = mask_bitmap, + .mask_kind = mask_kind, + .opacity = opacity }); +} + +void RecordingPainter::draw_triangle_wave(Gfx::IntPoint a_p1, Gfx::IntPoint a_p2, Color color, int amplitude, int thickness = 1) +{ + push_command(DrawTriangleWave { + .p1 = a_p1, + .p2 = a_p2, + .color = color, + .amplitude = amplitude, + .thickness = thickness }); +} + +void RecordingPainter::execute(Gfx::Bitmap& bitmap) +{ + CommandExecutionState state; + state.stacking_contexts.append(CommandExecutionState::StackingContext { + .painter = Gfx::Painter(bitmap), + .destination = Gfx::IntRect { 0, 0, 0, 0 }, + .opacity = 1, + }); + + size_t next_command_index = 0; + while (next_command_index < m_painting_commands.size()) { + auto& command = m_painting_commands[next_command_index++]; + auto result = command.visit([&](auto const& command) { return command.execute(state); }); + + if (result == CommandResult::SkipStackingContext) { + auto stacking_context_nesting_level = 1; + while (next_command_index < m_painting_commands.size()) { + if (m_painting_commands[next_command_index].has()) { + stacking_context_nesting_level++; + } else if (m_painting_commands[next_command_index].has()) { + stacking_context_nesting_level--; + } + + next_command_index++; + + if (stacking_context_nesting_level == 0) + break; + } + } + } + + VERIFY(state.stacking_contexts.size() == 1); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/RecordingPainter.h b/Userland/Libraries/LibWeb/Painting/RecordingPainter.h new file mode 100644 index 00000000000..f4742c8b9b0 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/RecordingPainter.h @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2023, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Painting { + +struct CommandExecutionState; + +enum class CommandResult { + Continue, + SkipStackingContext, +}; + +struct ClearRect { + Gfx::IntRect rect; + Color color; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct DrawTextRun { + Color color; + Gfx::IntPoint baseline_start; + String string; + NonnullRefPtr font; + Gfx::IntRect rect; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct DrawText { + Gfx::IntRect rect; + String raw_text; + Gfx::TextAlignment alignment; + Color color; + Gfx::TextElision elision; + Gfx::TextWrapping wrapping; + Optional> font {}; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct FillRect { + Gfx::IntRect rect; + Color color; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct DrawScaledBitmap { + Gfx::IntRect dst_rect; + NonnullRefPtr bitmap; + Gfx::IntRect src_rect; + float opacity; + Gfx::Painter::ScalingMode scaling_mode; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct Translate { + Gfx::IntPoint translation_delta; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct SaveState { + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct RestoreState { + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct AddClipRect { + Gfx::IntRect rect; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct ClearClipRect { + CommandResult execute(CommandExecutionState&) const; +}; + +struct SetFont { + NonnullRefPtr font; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PushStackingContext { + bool semitransparent_or_has_non_identity_transform; + bool has_fixed_position; + float opacity; + Gfx::FloatRect source_rect; + Gfx::FloatRect transformed_destination_rect; + DevicePixelPoint painter_location; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PopStackingContext { + bool semitransparent_or_has_non_identity_transform; + Gfx::Painter::ScalingMode scaling_mode; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PushStackingContextWithMask { + DevicePixelRect paint_rect; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PopStackingContextWithMask { + DevicePixelRect paint_rect; + RefPtr mask_bitmap; + Gfx::Bitmap::MaskKind mask_kind; + float opacity; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PaintLinearGradient { + Gfx::IntRect gradient_rect; + LinearGradientData linear_gradient_data; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PaintOuterBoxShadow { + PaintOuterBoxShadowParams outer_box_shadow_params; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PaintInnerBoxShadow { + PaintOuterBoxShadowParams outer_box_shadow_params; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PaintTextShadow { + DevicePixels blur_radius; + DevicePixelRect bounding_rect; + DevicePixelRect text_rect; + String text; + NonnullRefPtr font; + Color color; + DevicePixels fragment_baseline; + DevicePixelPoint draw_location; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct FillRectWithRoundedCorners { + Gfx::IntRect rect; + Color color; + Gfx::AntiAliasingPainter::CornerRadius top_left_radius; + Gfx::AntiAliasingPainter::CornerRadius top_right_radius; + Gfx::AntiAliasingPainter::CornerRadius bottom_left_radius; + Gfx::AntiAliasingPainter::CornerRadius bottom_right_radius; + Optional aa_translation {}; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct FillPathUsingColor { + Gfx::Path path; + Color color; + Gfx::Painter::WindingRule winding_rule; + Optional aa_translation {}; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct FillPathUsingPaintStyle { + Gfx::Path path; + NonnullRefPtr paint_style; + Gfx::Painter::WindingRule winding_rule; + float opacity; + Optional aa_translation {}; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct StrokePathUsingColor { + Gfx::Path path; + Color color; + float thickness; + Optional aa_translation {}; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct StrokePathUsingPaintStyle { + Gfx::Path path; + NonnullRefPtr paint_style; + float thickness; + float opacity = 1.0f; + Optional aa_translation {}; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct DrawEllipse { + Gfx::IntRect rect; + Color color; + int thickness; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct FillElipse { + Gfx::IntRect rect; + Color color; + Gfx::AntiAliasingPainter::BlendMode blend_mode; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct DrawLine { + Color color; + Gfx::IntPoint from; + Gfx::IntPoint to; + int thickness; + Gfx::Painter::LineStyle style; + Color alternate_color; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct DrawSignedDistanceField { + Gfx::IntRect rect; + Color color; + Gfx::GrayscaleBitmap sdf; + float smoothing; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PaintProgressbar { + Gfx::IntRect frame_rect; + Gfx::IntRect progress_rect; + Palette palette; + int min; + int max; + int value; + StringView text; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PaintFrame { + Gfx::IntRect rect; + Palette palette; + Gfx::FrameStyle style; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct ApplyBackdropFilter { + Gfx::IntRect backdrop_region; + BorderRadiiData border_radii_data; + CSS::ResolvedBackdropFilter backdrop_filter; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct DrawRect { + Gfx::IntRect rect; + Color color; + bool rough; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PaintRadialGradient { + Gfx::IntRect rect; + RadialGradientData radial_gradient_data; + Gfx::IntPoint center; + Gfx::IntSize size; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct PaintConicGradient { + Gfx::IntRect rect; + ConicGradientData conic_gradient_data; + Gfx::IntPoint position; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct DrawTriangleWave { + Gfx::IntPoint p1; + Gfx::IntPoint p2; + Color color; + int amplitude; + int thickness; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct SampleUnderCorners { + NonnullRefPtr corner_clipper; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +struct BlitCornerClipping { + NonnullRefPtr corner_clipper; + + [[nodiscard]] CommandResult execute(CommandExecutionState&) const; +}; + +using PaintingCommand = Variant< + ClearRect, + DrawTextRun, + DrawText, + FillRect, + DrawScaledBitmap, + Translate, + SaveState, + RestoreState, + AddClipRect, + ClearClipRect, + SetFont, + PushStackingContext, + PopStackingContext, + PushStackingContextWithMask, + PopStackingContextWithMask, + PaintLinearGradient, + PaintRadialGradient, + PaintConicGradient, + PaintOuterBoxShadow, + PaintInnerBoxShadow, + PaintTextShadow, + FillRectWithRoundedCorners, + FillPathUsingColor, + FillPathUsingPaintStyle, + StrokePathUsingColor, + StrokePathUsingPaintStyle, + DrawEllipse, + FillElipse, + DrawLine, + DrawSignedDistanceField, + PaintProgressbar, + PaintFrame, + ApplyBackdropFilter, + DrawRect, + DrawTriangleWave, + SampleUnderCorners, + BlitCornerClipping>; + +class RecordingPainter { +public: + void clear_rect(Gfx::IntRect const& rect, Color color); + void fill_rect(Gfx::IntRect const& rect, Color color); + + struct FillPathUsingColorParams { + Gfx::Path path; + Gfx::Color color; + Gfx::Painter::WindingRule winding_rule = Gfx::Painter::WindingRule::EvenOdd; + Optional translation = {}; + }; + void fill_path(FillPathUsingColorParams params); + + struct FillPathUsingPaintStyleParams { + Gfx::Path path; + NonnullRefPtr paint_style; + Gfx::Painter::WindingRule winding_rule = Gfx::Painter::WindingRule::EvenOdd; + float opacity; + Optional translation = {}; + }; + void fill_path(FillPathUsingPaintStyleParams params); + + struct StrokePathUsingColorParams { + Gfx::Path path; + Gfx::Color color; + float thickness; + Optional translation = {}; + }; + void stroke_path(StrokePathUsingColorParams params); + + struct StrokePathUsingPaintStyleParams { + Gfx::Path path; + NonnullRefPtr paint_style; + float thickness; + float opacity; + Optional translation = {}; + }; + void stroke_path(StrokePathUsingPaintStyleParams params); + + void draw_ellipse(Gfx::IntRect const& a_rect, Color color, int thickness); + + void fill_ellipse(Gfx::IntRect const& a_rect, Color color, Gfx::AntiAliasingPainter::BlendMode blend_mode = Gfx::AntiAliasingPainter::BlendMode::Normal); + + void fill_rect_with_linear_gradient(Gfx::IntRect const& gradient_rect, LinearGradientData const& data); + void fill_rect_with_conic_gradient(Gfx::IntRect const& rect, ConicGradientData const& data, Gfx::IntPoint const& position); + void fill_rect_with_radial_gradient(Gfx::IntRect const& rect, RadialGradientData const& data, DevicePixelPoint center, DevicePixelSize size); + + void draw_rect(Gfx::IntRect const& rect, Color color, bool rough = false); + + void draw_scaled_bitmap(Gfx::IntRect const& dst_rect, Gfx::Bitmap const& bitmap, Gfx::IntRect const& src_rect, float opacity = 1.0f, Gfx::Painter::ScalingMode scaling_mode = Gfx::Painter::ScalingMode::NearestNeighbor); + + void draw_line(Gfx::IntPoint from, Gfx::IntPoint to, Color color, int thickness = 1, Gfx::Painter::LineStyle style = Gfx::Painter::LineStyle::Solid, Color alternate_color = Color::Transparent); + + void draw_text(Gfx::IntRect const&, StringView, Gfx::Font const&, Gfx::TextAlignment = Gfx::TextAlignment::TopLeft, Color = Color::Black, Gfx::TextElision = Gfx::TextElision::None, Gfx::TextWrapping = Gfx::TextWrapping::DontWrap); + void draw_text(Gfx::IntRect const& rect, StringView raw_text, Gfx::TextAlignment alignment = Gfx::TextAlignment::TopLeft, Color color = Color::Black, Gfx::TextElision elision = Gfx::TextElision::None, Gfx::TextWrapping wrapping = Gfx::TextWrapping::DontWrap); + + void draw_signed_distance_field(Gfx::IntRect const& dst_rect, Color color, Gfx::GrayscaleBitmap const& sdf, float smoothing); + + // Streamlined text drawing routine that does no wrapping/elision/alignment. + void draw_text_run(Gfx::IntPoint baseline_start, Utf8View string, Gfx::Font const& font, Color color, Gfx::IntRect const& rect); + + void add_clip_rect(Gfx::IntRect const& rect); + void clear_clip_rect(); + + void translate(int dx, int dy); + void translate(Gfx::IntPoint delta); + + void set_font(Gfx::Font const& font); + + void save(); + void restore(); + + struct PushStackingContextParams { + bool semitransparent_or_has_non_identity_transform; + bool has_fixed_position; + float opacity; + Gfx::FloatRect source_rect; + Gfx::FloatRect transformed_destination_rect; + DevicePixelPoint painter_location; + }; + void push_stacking_context(PushStackingContextParams params); + + struct PopStackingContextParams { + bool semitransparent_or_has_non_identity_transform; + Gfx::Painter::ScalingMode scaling_mode; + }; + void pop_stacking_context(PopStackingContextParams params); + + void push_stacking_context_with_mask(DevicePixelRect paint_rect); + void pop_stacking_context_with_mask(RefPtr mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, DevicePixelRect paint_rect, float opacity); + + void sample_under_corners(NonnullRefPtr corner_clipper); + void blit_corner_clipping(NonnullRefPtr corner_clipper); + + void paint_progressbar(Gfx::IntRect frame_rect, Gfx::IntRect progress_rect, Palette palette, int min, int max, int value, StringView text); + void paint_frame(Gfx::IntRect rect, Palette palette, Gfx::FrameStyle style); + + void apply_backdrop_filter(DevicePixelRect const& backdrop_region, BorderRadiiData const& border_radii_data, CSS::ResolvedBackdropFilter const& backdrop_filter); + + void paint_outer_box_shadow_params(PaintOuterBoxShadowParams params); + void paint_inner_box_shadow_params(PaintOuterBoxShadowParams params); + void paint_text_shadow(DevicePixels blur_radius, DevicePixelRect bounding_rect, DevicePixelRect text_rect, Utf8View text, Gfx::Font const& font, Color color, DevicePixels fragment_baseline, DevicePixelPoint draw_location); + + void fill_rect_with_rounded_corners(Gfx::IntRect const& rect, Color color, Gfx::AntiAliasingPainter::CornerRadius top_left_radius, Gfx::AntiAliasingPainter::CornerRadius top_right_radius, Gfx::AntiAliasingPainter::CornerRadius bottom_right_radius, Gfx::AntiAliasingPainter::CornerRadius bottom_left_radius); + void fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect, Color color, int radius); + void fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect, Color color, int top_left_radius, int top_right_radius, int bottom_right_radius, int bottom_left_radius); + + void draw_triangle_wave(Gfx::IntPoint a_p1, Gfx::IntPoint a_p2, Color color, int amplitude, int thickness); + + void execute(Gfx::Bitmap&); + +private: + void push_command(PaintingCommand command) + { + m_painting_commands.append(command); + } + + Vector m_painting_commands; +}; + +class RecordingPainterStateSaver { +public: + explicit RecordingPainterStateSaver(RecordingPainter& painter) + : m_painter(painter) + { + m_painter.save(); + } + + ~RecordingPainterStateSaver() + { + m_painter.restore(); + } + +private: + RecordingPainter& m_painter; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp index beb80fd7c80..0a3d8eac8bb 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp @@ -69,12 +69,10 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const auto const* svg_element = geometry_element.shadow_including_first_ancestor_of_type(); auto svg_element_rect = svg_element->paintable_box()->absolute_rect(); - Gfx::AntiAliasingPainter painter { context.painter() }; - // FIXME: This should not be trucated to an int. - Gfx::PainterStateSaver save_painter { context.painter() }; + RecordingPainterStateSaver save_painter { context.painter() }; + auto offset = context.floored_device_point(svg_element_rect.location()).to_type().to_type(); - painter.translate(offset); auto maybe_view_box = geometry_element.view_box(); @@ -115,16 +113,20 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const auto fill_opacity = geometry_element.fill_opacity().value_or(1); auto winding_rule = to_gfx_winding_rule(geometry_element.fill_rule().value_or(SVG::FillRule::Nonzero)); if (auto paint_style = geometry_element.fill_paint_style(paint_context); paint_style.has_value()) { - painter.fill_path( - closed_path(), - *paint_style, - fill_opacity, - winding_rule); + context.painter().fill_path({ + .path = closed_path(), + .paint_style = *paint_style, + .winding_rule = winding_rule, + .opacity = fill_opacity, + .translation = offset, + }); } else if (auto fill_color = geometry_element.fill_color(); fill_color.has_value()) { - painter.fill_path( - closed_path(), - fill_color->with_opacity(fill_opacity), - winding_rule); + context.painter().fill_path({ + .path = closed_path(), + .color = fill_color->with_opacity(fill_opacity), + .winding_rule = winding_rule, + .translation = offset, + }); } auto stroke_opacity = geometry_element.stroke_opacity().value_or(1); @@ -133,16 +135,20 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const float stroke_thickness = geometry_element.stroke_width().value_or(1) * viewbox_scale; if (auto paint_style = geometry_element.stroke_paint_style(paint_context); paint_style.has_value()) { - painter.stroke_path( - path, - *paint_style, - stroke_thickness, - stroke_opacity); + context.painter().stroke_path({ + .path = path, + .paint_style = *paint_style, + .thickness = stroke_thickness, + .opacity = stroke_opacity, + .translation = offset, + }); } else if (auto stroke_color = geometry_element.stroke_color(); stroke_color.has_value()) { - painter.stroke_path( - path, - stroke_color->with_opacity(stroke_opacity), - stroke_thickness); + context.painter().stroke_path({ + .path = path, + .color = stroke_color->with_opacity(stroke_opacity), + .thickness = stroke_thickness, + .translation = offset, + }); } } diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp index 45ba30acc58..ceb23a0b458 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp @@ -64,14 +64,23 @@ static Gfx::Bitmap::MaskKind mask_type_to_gfx_mask_kind(CSS::MaskType mask_type) } } -void SVGGraphicsPaintable::apply_mask(PaintContext& context, Gfx::Bitmap& target, CSSPixelRect const& masking_area) const +Optional SVGGraphicsPaintable::get_mask_type() const +{ + auto const& graphics_element = verify_cast(*dom_node()); + auto mask = graphics_element.mask(); + if (!mask) + return {}; + return mask_type_to_gfx_mask_kind(mask->layout_node()->computed_values().mask_type()); +} + +RefPtr SVGGraphicsPaintable::calculate_mask(PaintContext& context, CSSPixelRect const& masking_area) const { auto const& graphics_element = verify_cast(*dom_node()); auto mask = graphics_element.mask(); VERIFY(mask); if (mask->mask_content_units() != SVG::MaskContentUnits::UserSpaceOnUse) { dbgln("SVG: maskContentUnits=objectBoundingBox is not supported"); - return; + return {}; } auto mask_rect = context.enclosing_device_rect(masking_area); RefPtr mask_bitmap = {}; @@ -79,20 +88,18 @@ void SVGGraphicsPaintable::apply_mask(PaintContext& context, Gfx::Bitmap& target auto& mask_paintable = static_cast(*mask->layout_node()->paintable()); auto mask_bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask_rect.size().to_type()); if (mask_bitmap_or_error.is_error()) - return; + return {}; mask_bitmap = mask_bitmap_or_error.release_value(); { - Gfx::Painter painter(*mask_bitmap); + RecordingPainter painter; painter.translate(-mask_rect.location().to_type()); auto paint_context = context.clone(painter); paint_context.set_svg_transform(graphics_element.get_transform()); StackingContext::paint_node_as_stacking_context(mask_paintable, paint_context); + painter.execute(*mask_bitmap); } } - if (mask_bitmap) - target.apply_mask(*mask_bitmap, - mask_type_to_gfx_mask_kind(mask->layout_node()->computed_values().mask_type())); - return; + return mask_bitmap; } } diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h index a64f60d205b..08a92c17cc2 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.h @@ -22,7 +22,8 @@ public: virtual bool forms_unconnected_subtree() const override; virtual Optional get_masking_area() const override; - virtual void apply_mask(PaintContext&, Gfx::Bitmap& target, CSSPixelRect const& masking_area) const override; + virtual Optional get_mask_type() const override; + virtual RefPtr calculate_mask(PaintContext&, CSSPixelRect const& masking_area) const override; protected: SVGGraphicsPaintable(Layout::SVGGraphicsBox const&); diff --git a/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp index e4f746353c5..870ad89ec64 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp @@ -50,7 +50,7 @@ void SVGTextPaintable::paint(PaintContext& context, PaintPhase phase) const auto const* svg_element = text_element.shadow_including_first_ancestor_of_type(); auto svg_element_rect = svg_element->paintable_box()->absolute_rect(); - Gfx::PainterStateSaver save_painter { painter }; + RecordingPainterStateSaver save_painter { painter }; auto svg_context_offset = context.floored_device_point(svg_element_rect.location()).to_type(); painter.translate(svg_context_offset); @@ -95,7 +95,7 @@ void SVGTextPaintable::paint(PaintContext& context, PaintPhase phase) const VERIFY_NOT_REACHED(); } - painter.draw_text_run(text_offset.to_type(), text_content, scaled_font, layout_node().computed_values().fill()->as_color()); + painter.draw_text_run(text_offset.to_type(), text_content, scaled_font, layout_node().computed_values().fill()->as_color(), context.enclosing_device_rect(svg_element_rect).to_type()); } } diff --git a/Userland/Libraries/LibWeb/Painting/ShadowData.h b/Userland/Libraries/LibWeb/Painting/ShadowData.h new file mode 100644 index 00000000000..0db555520d0 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/ShadowData.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021-2022, Sam Atkins + * Copyright (c) 2022, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Painting { + +enum class ShadowPlacement { + Outer, + Inner, +}; + +struct ShadowData { + Gfx::Color color; + CSSPixels offset_x; + CSSPixels offset_y; + CSSPixels blur_radius; + CSSPixels spread_distance; + ShadowPlacement placement; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp b/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp index 32c2264ff83..9cf4d16988a 100644 --- a/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp @@ -9,29 +9,26 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include namespace Web::Painting { -static void paint_inner_box_shadow(PaintContext& context, CSSPixelRect const& content_rect, - BordersData const& borders_data, BorderRadiiData const& border_radii, ShadowData const& box_shadow_data) +void paint_inner_box_shadow(Gfx::Painter& painter, PaintOuterBoxShadowParams params) { - auto& painter = context.painter(); - auto device_content_rect = context.rounded_device_rect(content_rect); + auto device_content_rect = params.device_content_rect; - auto border_radii_shrunken = border_radii; - border_radii_shrunken.shrink(borders_data.top.width, borders_data.right.width, borders_data.bottom.width, borders_data.left.width); - ScopedCornerRadiusClip corner_clipper { context, painter, device_content_rect, border_radii_shrunken, CornerClip::Outside }; - DevicePixels offset_x = context.rounded_device_pixels(box_shadow_data.offset_x); - DevicePixels offset_y = context.rounded_device_pixels(box_shadow_data.offset_y); - DevicePixels blur_radius = context.rounded_device_pixels(box_shadow_data.blur_radius); - DevicePixels spread_distance = context.rounded_device_pixels(box_shadow_data.spread_distance); + DevicePixels offset_x = params.offset_x; + DevicePixels offset_y = params.offset_y; + DevicePixels blur_radius = params.blur_radius; + DevicePixels spread_distance = params.spread_distance; auto shadows_bitmap_rect = device_content_rect.inflated( blur_radius.value() + offset_y.value(), blur_radius.value() + abs(offset_x.value()), @@ -55,43 +52,42 @@ static void paint_inner_box_shadow(PaintContext& context, CSSPixelRect const& co blur_radius.value() + abs(offset_x.value()), blur_radius.value() + abs(offset_y.value()), blur_radius.value() + offset_x.value()); - auto top_left_corner = border_radii_shrunken.top_left.as_corner(context); - auto top_right_corner = border_radii_shrunken.top_right.as_corner(context); - auto bottom_right_corner = border_radii_shrunken.bottom_right.as_corner(context); - auto bottom_left_corner = border_radii_shrunken.bottom_left.as_corner(context); - shadow_painter.fill_rect(outer_shadow_rect, box_shadow_data.color.with_alpha(0xff)); - if (border_radii_shrunken.has_any_radius()) { - shadow_aa_painter.fill_rect_with_rounded_corners(inner_shadow_rect, box_shadow_data.color.with_alpha(0xff), + auto top_left_corner = params.corner_radii.top_left; + auto top_right_corner = params.corner_radii.top_right; + auto bottom_right_corner = params.corner_radii.bottom_right; + auto bottom_left_corner = params.corner_radii.bottom_left; + shadow_painter.fill_rect(outer_shadow_rect, params.box_shadow_data.color.with_alpha(0xff)); + if (params.border_radii.has_any_radius()) { + shadow_aa_painter.fill_rect_with_rounded_corners(inner_shadow_rect, params.box_shadow_data.color.with_alpha(0xff), top_left_corner, top_right_corner, bottom_right_corner, bottom_left_corner, Gfx::AntiAliasingPainter::BlendMode::AlphaSubtract); } else { shadow_painter.clear_rect(inner_shadow_rect, Color::Transparent); } Gfx::StackBlurFilter filter(*shadow_bitmap); - filter.process_rgba(blur_radius.value(), box_shadow_data.color); + filter.process_rgba(blur_radius.value(), params.box_shadow_data.color); Gfx::PainterStateSaver save { painter }; painter.add_clip_rect(device_content_rect_int); painter.blit({ device_content_rect_int.left() - blur_radius.value(), device_content_rect_int.top() - blur_radius.value() }, - *shadow_bitmap, shadow_bitmap->rect(), box_shadow_data.color.alpha() / 255.); + *shadow_bitmap, shadow_bitmap->rect(), params.box_shadow_data.color.alpha() / 255.); } -static void paint_outer_box_shadow(PaintContext& context, CSSPixelRect const& content_rect, - BorderRadiiData const& border_radii, ShadowData const& box_shadow_data) +void paint_outer_box_shadow(Gfx::Painter& painter, PaintOuterBoxShadowParams params) { - auto& painter = context.painter(); - auto device_content_rect = context.rounded_device_rect(content_rect); + auto const& border_radii = params.border_radii; + auto const& box_shadow_data = params.box_shadow_data; - auto top_left_corner = border_radii.top_left.as_corner(context); - auto top_right_corner = border_radii.top_right.as_corner(context); - auto bottom_right_corner = border_radii.bottom_right.as_corner(context); - auto bottom_left_corner = border_radii.bottom_left.as_corner(context); + auto device_content_rect = params.device_content_rect; - ScopedCornerRadiusClip corner_clipper { context, painter, device_content_rect, border_radii, CornerClip::Inside }; + auto top_left_corner = params.corner_radii.top_left; + auto top_right_corner = params.corner_radii.top_right; + auto bottom_right_corner = params.corner_radii.bottom_right; + auto bottom_left_corner = params.corner_radii.bottom_left; - DevicePixels offset_x = context.rounded_device_pixels(box_shadow_data.offset_x); - DevicePixels offset_y = context.rounded_device_pixels(box_shadow_data.offset_y); - DevicePixels blur_radius = context.rounded_device_pixels(box_shadow_data.blur_radius); - DevicePixels spread_distance = context.rounded_device_pixels(box_shadow_data.spread_distance); + DevicePixels offset_x = params.offset_x; + DevicePixels offset_y = params.offset_y; + DevicePixels blur_radius = params.blur_radius; + DevicePixels spread_distance = params.spread_distance; auto fill_rect_masked = [](auto& painter, auto fill_rect, auto mask_rect, auto color) { Gfx::DisjointRectSet rect_set; @@ -233,7 +229,7 @@ static void paint_outer_box_shadow(PaintContext& context, CSSPixelRect const& co filter.process_rgba(blur_radius.value(), box_shadow_data.color); auto paint_shadow_infill = [&] { - if (!border_radii.has_any_radius()) + if (!params.border_radii.has_any_radius()) return painter.fill_rect(inner_bounding_rect.to_type(), box_shadow_data.color); auto top_left_inner_width = top_left_corner_rect.width() - blurred_edge_thickness; @@ -395,10 +391,43 @@ void paint_box_shadow(PaintContext& context, { // Note: Box-shadow layers are ordered front-to-back, so we paint them in reverse for (auto& box_shadow_data : box_shadow_layers.in_reverse()) { + DevicePixels offset_x = context.rounded_device_pixels(box_shadow_data.offset_x); + DevicePixels offset_y = context.rounded_device_pixels(box_shadow_data.offset_y); + DevicePixels blur_radius = context.rounded_device_pixels(box_shadow_data.blur_radius); + DevicePixels spread_distance = context.rounded_device_pixels(box_shadow_data.spread_distance); + + DevicePixelRect device_content_rect; if (box_shadow_data.placement == ShadowPlacement::Inner) { - paint_inner_box_shadow(context, borderless_content_rect, borders_data, border_radii, box_shadow_data); + device_content_rect = context.rounded_device_rect(borderless_content_rect); } else { - paint_outer_box_shadow(context, bordered_content_rect, border_radii, box_shadow_data); + device_content_rect = context.rounded_device_rect(bordered_content_rect); + } + + auto params = PaintOuterBoxShadowParams { + .painter = context.painter(), + .content_rect = bordered_content_rect, + .border_radii = border_radii, + .box_shadow_data = box_shadow_data, + .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) }, + .offset_x = offset_x, + .offset_y = offset_y, + .blur_radius = blur_radius, + .spread_distance = spread_distance, + .device_content_rect = device_content_rect, + }; + + params.border_radii.shrink(borders_data.top.width, borders_data.right.width, borders_data.bottom.width, borders_data.left.width); + + if (box_shadow_data.placement == ShadowPlacement::Inner) { + ScopedCornerRadiusClip corner_clipper { context, device_content_rect, border_radii, CornerClip::Outside }; + context.painter().paint_inner_box_shadow_params(params); + } else { + ScopedCornerRadiusClip corner_clipper { context, device_content_rect, params.border_radii, CornerClip::Inside }; + context.painter().paint_outer_box_shadow_params(params); } } } @@ -408,15 +437,18 @@ void paint_text_shadow(PaintContext& context, Layout::LineBoxFragment const& fra if (shadow_layers.is_empty() || fragment.text().is_empty()) return; - auto& painter = context.painter(); + auto fragment_width = context.enclosing_device_pixels(fragment.width()); + auto fragment_height = context.enclosing_device_pixels(fragment.height()); + auto draw_rect = context.enclosing_device_rect(fragment.absolute_rect()); + auto text = Utf8View(fragment.text()); + auto& font = fragment.layout_node().scaled_font(context); + auto fragment_baseline = context.rounded_device_pixels(fragment.baseline()); // Note: Box-shadow layers are ordered front-to-back, so we paint them in reverse for (auto& layer : shadow_layers.in_reverse()) { DevicePixels offset_x = context.rounded_device_pixels(layer.offset_x); DevicePixels offset_y = context.rounded_device_pixels(layer.offset_y); DevicePixels blur_radius = context.rounded_device_pixels(layer.blur_radius); - DevicePixels fragment_width = context.enclosing_device_pixels(fragment.width()); - DevicePixels fragment_height = context.enclosing_device_pixels(fragment.height()); // Space around the painted text to allow it to blur. // FIXME: Include spread in this once we use that. @@ -430,29 +462,12 @@ void paint_text_shadow(PaintContext& context, Layout::LineBoxFragment const& fra text_rect.width() + margin + margin, text_rect.height() + margin + margin }; - // FIXME: Figure out the maximum bitmap size for all shadows and then allocate it once and reuse it? - auto maybe_shadow_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, bounding_rect.size().to_type()); - if (maybe_shadow_bitmap.is_error()) { - dbgln("Unable to allocate temporary bitmap {} for text-shadow rendering: {}", bounding_rect.size(), maybe_shadow_bitmap.error()); - return; - } - auto shadow_bitmap = maybe_shadow_bitmap.release_value(); - - Gfx::Painter shadow_painter { *shadow_bitmap }; - // FIXME: "Spread" the shadow somehow. - DevicePixelPoint baseline_start(text_rect.x(), text_rect.y() + context.rounded_device_pixels(fragment.baseline())); - shadow_painter.draw_text_run(baseline_start.to_type(), Utf8View(fragment.text()), fragment.layout_node().scaled_font(context), layer.color); - - // Blur - Gfx::StackBlurFilter filter(*shadow_bitmap); - filter.process_rgba(blur_radius.value(), layer.color); - - auto draw_rect = context.enclosing_device_rect(fragment.absolute_rect()); DevicePixelPoint draw_location { draw_rect.x() + offset_x - margin, draw_rect.y() + offset_y - margin }; - painter.blit(draw_location.to_type(), *shadow_bitmap, bounding_rect.to_type()); + + context.painter().paint_text_shadow(blur_radius, bounding_rect, text_rect, text, font, layer.color, fragment_baseline, draw_location); } } diff --git a/Userland/Libraries/LibWeb/Painting/ShadowPainting.h b/Userland/Libraries/LibWeb/Painting/ShadowPainting.h index 7a387b7c90e..f9454b65b54 100644 --- a/Userland/Libraries/LibWeb/Painting/ShadowPainting.h +++ b/Userland/Libraries/LibWeb/Painting/ShadowPainting.h @@ -9,22 +9,13 @@ #include #include #include +#include +#include namespace Web::Painting { -enum class ShadowPlacement { - Outer, - Inner, -}; - -struct ShadowData { - Gfx::Color color; - CSSPixels offset_x; - CSSPixels offset_y; - CSSPixels blur_radius; - CSSPixels spread_distance; - ShadowPlacement placement; -}; +void paint_outer_box_shadow(Gfx::Painter&, PaintOuterBoxShadowParams params); +void paint_inner_box_shadow(Gfx::Painter&, PaintOuterBoxShadowParams params); void paint_box_shadow( PaintContext&, diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index b14dc28d392..e8fd471ff0b 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -290,10 +289,7 @@ Gfx::AffineTransform StackingContext::affine_transform_matrix() const void StackingContext::paint(PaintContext& context) const { - Gfx::PainterStateSaver saver(context.painter()); - if (paintable_box().is_fixed_position()) { - context.painter().translate(-context.painter().translation()); - } + RecordingPainterStateSaver saver(context.painter()); auto opacity = paintable_box().computed_values().opacity(); if (opacity == 0.0f) @@ -305,18 +301,12 @@ void StackingContext::paint(PaintContext& context) const if (masking_area->is_empty()) return; auto paint_rect = context.enclosing_device_rect(*masking_area); - auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, paint_rect.size().to_type()); - if (bitmap_or_error.is_error()) - return; - auto bitmap = bitmap_or_error.release_value(); - { - Gfx::Painter painter(bitmap); - painter.translate(-paint_rect.location().to_type()); - auto paint_context = context.clone(painter); - paint_internal(paint_context); - } - paintable_box().apply_mask(context, bitmap, *masking_area); - context.painter().blit(paint_rect.location().to_type(), *bitmap, bitmap->rect(), opacity); + context.painter().push_stacking_context_with_mask(paint_rect); + paint_internal(context); + + auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area); + auto mask_type = paintable_box().get_mask_type(); + context.painter().pop_stacking_context_with_mask(mask_bitmap, *mask_type, paint_rect, opacity); return; } @@ -324,52 +314,40 @@ void StackingContext::paint(PaintContext& context) const auto translation = context.rounded_device_point(affine_transform.translation().to_type()).to_type().to_type(); affine_transform.set_translation(translation); + auto transform_origin = this->transform_origin(); + auto source_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type().to_type().translated(-transform_origin); + auto transformed_destination_rect = affine_transform.map(source_rect).translated(transform_origin); + auto destination_rect = transformed_destination_rect.to_rounded(); + + RecordingPainter::PushStackingContextParams push_stacking_context_params { + .semitransparent_or_has_non_identity_transform = false, + .has_fixed_position = false, + .opacity = opacity, + .source_rect = source_rect, + .transformed_destination_rect = transformed_destination_rect, + .painter_location = context.rounded_device_point(-paintable_box().absolute_paint_rect().location()) + }; + + RecordingPainter::PopStackingContextParams pop_stacking_context_params { + .semitransparent_or_has_non_identity_transform = false, + .scaling_mode = CSS::to_gfx_scaling_mode(paintable_box().computed_values().image_rendering(), destination_rect, destination_rect) + }; + + if (paintable_box().is_fixed_position()) { + push_stacking_context_params.has_fixed_position = true; + } + if (opacity < 1.0f || !affine_transform.is_identity_or_translation()) { - auto transform_origin = this->transform_origin(); - auto source_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type().to_type().translated(-transform_origin); - auto transformed_destination_rect = affine_transform.map(source_rect).translated(transform_origin); - auto destination_rect = transformed_destination_rect.to_rounded(); - - // FIXME: We should find a way to scale the paintable, rather than paint into a separate bitmap, - // then scale it. This snippet now copies the background at the destination, then scales it down/up - // to the size of the source (which could add some artefacts, though just scaling the bitmap already does that). - // We need to copy the background at the destination because a bunch of our rendering effects now rely on - // being able to sample the painter (see border radii, shadows, filters, etc). - Gfx::FloatPoint destination_clipped_fixup {}; - auto try_get_scaled_destination_bitmap = [&]() -> ErrorOr> { - Gfx::IntRect actual_destination_rect; - auto bitmap = TRY(context.painter().get_region_bitmap(destination_rect, Gfx::BitmapFormat::BGRA8888, actual_destination_rect)); - // get_region_bitmap() may clip to a smaller region if the requested rect goes outside the painter, so we need to account for that. - destination_clipped_fixup = Gfx::FloatPoint { destination_rect.location() - actual_destination_rect.location() }; - destination_rect = actual_destination_rect; - if (source_rect.size() != transformed_destination_rect.size()) { - auto sx = static_cast(source_rect.width()) / transformed_destination_rect.width(); - auto sy = static_cast(source_rect.height()) / transformed_destination_rect.height(); - bitmap = TRY(bitmap->scaled(sx, sy)); - destination_clipped_fixup.scale_by(sx, sy); - } - return bitmap; - }; - - auto bitmap_or_error = try_get_scaled_destination_bitmap(); - if (bitmap_or_error.is_error()) - return; - auto bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors(); - Gfx::Painter painter(bitmap); - painter.translate(context.rounded_device_point(-paintable_box().absolute_paint_rect().location() + destination_clipped_fixup.to_type()).to_type()); - auto paint_context = context.clone(painter); - paint_internal(paint_context); - - if (destination_rect.size() == bitmap->size()) { - context.painter().blit(destination_rect.location(), *bitmap, bitmap->rect(), opacity); - } else { - auto scaling_mode = CSS::to_gfx_scaling_mode(paintable_box().computed_values().image_rendering(), bitmap->rect(), destination_rect); - context.painter().draw_scaled_bitmap(destination_rect, *bitmap, bitmap->rect(), opacity, scaling_mode); - } + push_stacking_context_params.semitransparent_or_has_non_identity_transform = true; + pop_stacking_context_params.semitransparent_or_has_non_identity_transform = true; + context.painter().push_stacking_context(push_stacking_context_params); + paint_internal(context); + context.painter().pop_stacking_context(pop_stacking_context_params); } else { - Gfx::PainterStateSaver saver(context.painter()); + context.painter().push_stacking_context(push_stacking_context_params); context.painter().translate(affine_transform.translation().to_rounded()); paint_internal(context); + context.painter().pop_stacking_context(pop_stacking_context_params); } } diff --git a/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp b/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp index ecfb9582b10..b03c79203ec 100644 --- a/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp @@ -284,8 +284,7 @@ static void paint_collected_edges(PaintContext& context, Vector(), p2.to_type(), color, width.value(), Gfx::Painter::LineStyle::Dotted); + context.painter().draw_line(p1.to_type(), p2.to_type(), color, width.value(), Gfx::Painter::LineStyle::Dotted); } else if (border_style == CSS::LineStyle::Dashed) { context.painter().draw_line(p1.to_type(), p2.to_type(), color, width.value(), Gfx::Painter::LineStyle::Dashed); } else { diff --git a/Userland/Libraries/LibWeb/Painting/VideoPaintable.cpp b/Userland/Libraries/LibWeb/Painting/VideoPaintable.cpp index a493b81fa6d..9038892bd81 100644 --- a/Userland/Libraries/LibWeb/Painting/VideoPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/VideoPaintable.cpp @@ -52,21 +52,17 @@ void VideoPaintable::paint(PaintContext& context, PaintPhase phase) const if (!is_visible()) return; - // FIXME: This should be done at a different level. - if (is_out_of_view(context)) - return; - Base::paint(context, phase); if (phase != PaintPhase::Foreground) return; - Gfx::PainterStateSaver saver { context.painter() }; + RecordingPainterStateSaver saver { context.painter() }; auto video_rect = context.rounded_device_rect(absolute_rect()); context.painter().add_clip_rect(video_rect.to_type()); - ScopedCornerRadiusClip corner_clip { context, context.painter(), video_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; + ScopedCornerRadiusClip corner_clip { context, video_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) }; auto const& video_element = layout_box().dom_node(); auto mouse_position = MediaPaintable::mouse_position(context, video_element); @@ -214,8 +210,7 @@ void VideoPaintable::paint_placeholder_video_controls(PaintContext& context, Dev auto playback_button_is_hovered = mouse_position.has_value() && control_box_rect.contains(*mouse_position); auto playback_button_color = control_button_color(playback_button_is_hovered); - Gfx::AntiAliasingPainter painter { context.painter() }; - painter.fill_ellipse(control_box_rect.to_type(), control_box_color); + context.painter().fill_ellipse(control_box_rect.to_type(), control_box_color); fill_triangle(context.painter(), playback_button_location.to_type(), play_button_coordinates, playback_button_color); } diff --git a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp index dd50b414b77..a2d1a7b07e9 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp @@ -106,10 +106,12 @@ void SVGDecodedImageData::render(Gfx::IntSize size) const m_document->navigable()->set_viewport_rect({ 0, 0, size.width(), size.height() }); m_document->update_layout(); - Gfx::Painter painter(*m_bitmap); - PaintContext context(painter, m_page_client->palette(), m_page_client->device_pixels_per_css_pixel()); + Painting::RecordingPainter recording_painter; + PaintContext context(recording_painter, m_page_client->palette(), m_page_client->device_pixels_per_css_pixel()); m_document->paintable()->paint_all_phases(context); + + recording_painter.execute(*m_bitmap); } RefPtr SVGDecodedImageData::bitmap(size_t, Gfx::IntSize size) const diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h index dee32abf289..33ed54e7103 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h +++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h @@ -8,7 +8,6 @@ #pragma once #include -#include #include #include #include diff --git a/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp index 6bc3b1c62c6..dfdafd22fb3 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include diff --git a/Userland/Services/WebContent/PageHost.cpp b/Userland/Services/WebContent/PageHost.cpp index ee591d58e7e..7d42911db98 100644 --- a/Userland/Services/WebContent/PageHost.cpp +++ b/Userland/Services/WebContent/PageHost.cpp @@ -7,7 +7,6 @@ #include "PageHost.h" #include "ConnectionFromClient.h" -#include #include #include #include @@ -121,7 +120,6 @@ Gfx::Color PageHost::background_color() const void PageHost::paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap& target) { - Gfx::Painter painter(target); Gfx::IntRect bitmap_rect { {}, content_rect.size().to_type() }; auto document = page().top_level_browsing_context().active_document(); @@ -131,18 +129,22 @@ void PageHost::paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap& targ auto background_color = this->background_color(); + Web::Painting::RecordingPainter recording_painter; + Web::PaintContext context(recording_painter, palette(), device_pixels_per_css_pixel()); + if (background_color.alpha() < 255) - painter.clear_rect(bitmap_rect, Web::CSS::SystemColor::canvas()); - painter.fill_rect(bitmap_rect, background_color); + recording_painter.clear_rect(bitmap_rect, Web::CSS::SystemColor::canvas()); + recording_painter.fill_rect(bitmap_rect, background_color); if (!document->paintable()) return; - Web::PaintContext context(painter, palette(), device_pixels_per_css_pixel()); context.set_should_show_line_box_borders(m_should_show_line_box_borders); context.set_device_viewport_rect(content_rect); context.set_has_focus(m_has_focus); document->paintable()->paint_all_phases(context); + + recording_painter.execute(target); } void PageHost::set_viewport_rect(Web::DevicePixelRect const& rect)