From 4e04f81626cf3a5e1bea52ae26bb17f40695ad78 Mon Sep 17 00:00:00 2001 From: MacDue Date: Sat, 18 Nov 2023 14:23:59 +0000 Subject: [PATCH] LibWeb: Don't encode painting limitations in RecordingPainter API The current set of stacking context commands do not encode the information needed to correctly paint the stacking context, instead, they're based on the limitations of the current CPU renderer. Stacking contexts should be able to be transformed by an arbitrary 3D transformation matrix, not just scaled from a source to a destination rect. The `_with_mask()` stacking context also should not be separate from the regular stacking context. ```c++ push_stacking_context( bool semitransparent_or_has_non_identity_transform, float opacity, Gfx::FloatRect const& source_rect, Gfx::FloatRect const& transformed_destination_rect, Gfx::IntPoint const& painter_location); pop_stacking_context( bool semitransparent_or_has_non_identity_transform, Gfx::Painter::ScalingMode scaling_mode); push_stacking_context_with_mask( Gfx::IntRect const& paint_rect); pop_stacking_context_with_mask( Gfx::IntRect const& paint_rect, RefPtr const& mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, float opacity); ``` This patch replaces this APIs with just: ```c++ push_stacking_context( float opacity, bool is_fixed_position, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering image_rendering, StackingContextTransform transform, Optional mask); pop_stacking_context() ``` And moves the implementation details into the executor, this should allow future backends to implement stacking contexts without these limitations. --- .../Painting/PaintingCommandExecutorCPU.cpp | 186 +++++++++--------- .../Painting/PaintingCommandExecutorCPU.h | 17 +- .../Painting/PaintingCommandExecutorGPU.cpp | 16 +- .../Painting/PaintingCommandExecutorGPU.h | 6 +- .../LibWeb/Painting/RecordingPainter.cpp | 65 ++---- .../LibWeb/Painting/RecordingPainter.h | 68 +++---- .../LibWeb/Painting/StackingContext.cpp | 84 +++----- 7 files changed, 189 insertions(+), 253 deletions(-) diff --git a/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorCPU.cpp b/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorCPU.cpp index 050f716db3..443e78c3fc 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorCPU.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorCPU.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -17,7 +18,10 @@ namespace Web::Painting { PaintingCommandExecutorCPU::PaintingCommandExecutorCPU(Gfx::Bitmap& bitmap) : m_target_bitmap(bitmap) { - stacking_contexts.append({ Gfx::Painter(bitmap), {}, 1.0f }); + stacking_contexts.append({ .painter = AK::make(bitmap), + .opacity = 1.0f, + .destination = {}, + .scaling_mode = {} }); } CommandResult PaintingCommandExecutorCPU::draw_glyph_run(Vector const& glyph_run, Color const& color) @@ -70,8 +74,7 @@ CommandResult PaintingCommandExecutorCPU::set_clip_rect(Gfx::IntRect const& rect CommandResult PaintingCommandExecutorCPU::clear_clip_rect() { - auto& painter = this->painter(); - painter.clear_clip_rect(); + painter().clear_clip_rect(); return CommandResult::Continue; } @@ -82,107 +85,112 @@ CommandResult PaintingCommandExecutorCPU::set_font(Gfx::Font const& font) return CommandResult::Continue; } -CommandResult PaintingCommandExecutorCPU::push_stacking_context(bool semitransparent_or_has_non_identity_transform, float opacity, Gfx::FloatRect const& source_rect, Gfx::FloatRect const& transformed_destination_rect, Gfx::IntPoint const& painter_location) +CommandResult PaintingCommandExecutorCPU::push_stacking_context( + float opacity, bool is_fixed_position, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, + CSS::ImageRendering image_rendering, StackingContextTransform transform, Optional mask) { - auto& painter = this->painter(); - 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 + destination_clipped_fixup.to_type()); + painter().save(); + if (is_fixed_position) + painter().translate(-painter().translation()); + if (mask.has_value()) { + // TODO: Support masks and other stacking context features at the same time. + // Note: Currently only SVG masking is implemented (which does not use CSS transforms anyway). + auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask->mask_bitmap->size()); + if (bitmap_or_error.is_error()) + return CommandResult::Continue; + auto bitmap = bitmap_or_error.release_value(); stacking_contexts.append(StackingContext { - .painter = stacking_context_painter, - .destination = destination_rect, - .opacity = opacity, - }); - } else { - painter.save(); + .painter = AK::make(bitmap), + .opacity = 1, + .destination = source_paintable_rect.translated(post_transform_translation), + .scaling_mode = Gfx::Painter::ScalingMode::None, + .mask = mask }); + painter().translate(-source_paintable_rect.location()); + return CommandResult::Continue; } + // FIXME: This extracts the affine 2D part of the full transformation matrix. + // Use the whole matrix when we get better transformation support in LibGfx or use LibGL for drawing the bitmap + auto affine_transform = Gfx::extract_2d_affine_transform(transform.matrix); + + if (opacity == 1.0f && affine_transform.is_identity_or_translation()) { + // OPTIMIZATION: This is a simple translation use previous stacking context's painter. + painter().translate(affine_transform.translation().to_rounded() + post_transform_translation); + stacking_contexts.append(StackingContext { + .painter = MaybeOwned(painter()), + .opacity = 1, + .destination = {}, + .scaling_mode = {} }); + return CommandResult::Continue; + } + + auto& current_painter = this->painter(); + auto source_rect = source_paintable_rect.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(current_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(); + stacking_contexts.append(StackingContext { + .painter = AK::make(bitmap), + .opacity = opacity, + .destination = destination_rect.translated(post_transform_translation), + .scaling_mode = CSS::to_gfx_scaling_mode(image_rendering, destination_rect, destination_rect) }); + painter().translate(-source_paintable_rect.location() + destination_clipped_fixup.to_type()); + return CommandResult::Continue; } -CommandResult PaintingCommandExecutorCPU::pop_stacking_context(bool semitransparent_or_has_non_identity_transform, Gfx::Painter::ScalingMode scaling_mode) +CommandResult PaintingCommandExecutorCPU::pop_stacking_context() { - if (semitransparent_or_has_non_identity_transform) { - auto stacking_context = stacking_contexts.take_last(); - auto bitmap = stacking_context.painter.target(); + ScopeGuard restore_painter = [&] { + painter().restore(); + }; + auto stacking_context = stacking_contexts.take_last(); + // Stacking contexts that don't own their painter are simple translations, and don't need to blit anything back. + if (stacking_context.painter.is_owned()) { + auto bitmap = stacking_context.painter->target(); + if (stacking_context.mask.has_value()) + bitmap->apply_mask(*stacking_context.mask->mask_bitmap, stacking_context.mask->mask_kind); auto destination_rect = stacking_context.destination; - if (destination_rect.size() == bitmap->size()) { painter().blit(destination_rect.location(), *bitmap, bitmap->rect(), stacking_context.opacity); } else { - painter().draw_scaled_bitmap(destination_rect, *bitmap, bitmap->rect(), stacking_context.opacity, scaling_mode); + painter().draw_scaled_bitmap(destination_rect, *bitmap, bitmap->rect(), stacking_context.opacity, stacking_context.scaling_mode); } - } else { - painter().restore(); } - - return CommandResult::Continue; -} - -CommandResult PaintingCommandExecutorCPU::push_stacking_context_with_mask(Gfx::IntRect const& paint_rect) -{ - auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, paint_rect.size()); - 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()); - - stacking_contexts.append(StackingContext { - .painter = stacking_context_painter, - .destination = {}, - .opacity = 1, - }); - - return CommandResult::Continue; -} - -CommandResult PaintingCommandExecutorCPU::pop_stacking_context_with_mask(Gfx::IntRect const& paint_rect, RefPtr const& mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, float opacity) -{ - auto stacking_context = stacking_contexts.take_last(); - auto bitmap = stacking_context.painter.target(); - if (mask_bitmap) - bitmap->apply_mask(*mask_bitmap, mask_kind); - painter().blit(paint_rect.location(), *bitmap, bitmap->rect(), opacity); return CommandResult::Continue; } diff --git a/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorCPU.h b/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorCPU.h index 1758a2cefb..c1f377737e 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorCPU.h +++ b/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorCPU.h @@ -6,6 +6,7 @@ #pragma once +#include #include namespace Web::Painting { @@ -19,10 +20,8 @@ public: CommandResult set_clip_rect(Gfx::IntRect const& rect) override; CommandResult clear_clip_rect() override; CommandResult set_font(Gfx::Font const&) override; - CommandResult push_stacking_context(bool semitransparent_or_has_non_identity_transform, float opacity, Gfx::FloatRect const& source_rect, Gfx::FloatRect const& transformed_destination_rect, Gfx::IntPoint const& painter_location) override; - CommandResult pop_stacking_context(bool semitransparent_or_has_non_identity_transform, Gfx::Painter::ScalingMode scaling_mode) override; - CommandResult push_stacking_context_with_mask(Gfx::IntRect const&) override; - CommandResult pop_stacking_context_with_mask(Gfx::IntRect const&, RefPtr const& mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, float opacity) override; + CommandResult push_stacking_context(float opacity, bool is_fixed_position, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering image_rendering, StackingContextTransform transform, Optional mask) override; + CommandResult pop_stacking_context() override; CommandResult paint_linear_gradient(Gfx::IntRect const&, Web::Painting::LinearGradientData const&) override; CommandResult paint_outer_box_shadow(PaintOuterBoxShadowParams const&) override; CommandResult paint_inner_box_shadow(PaintOuterBoxShadowParams const&) override; @@ -57,13 +56,15 @@ private: Gfx::Bitmap& m_target_bitmap; struct StackingContext { - Gfx::Painter painter; - Gfx::IntRect destination; + MaybeOwned painter; float opacity; + Gfx::IntRect destination; + Gfx::Painter::ScalingMode scaling_mode; + Optional mask = {}; }; - [[nodiscard]] Gfx::Painter const& painter() const { return stacking_contexts.last().painter; } - [[nodiscard]] Gfx::Painter& painter() { return stacking_contexts.last().painter; } + [[nodiscard]] Gfx::Painter const& painter() const { return *stacking_contexts.last().painter; } + [[nodiscard]] Gfx::Painter& painter() { return *stacking_contexts.last().painter; } Vector stacking_contexts; }; diff --git a/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorGPU.cpp b/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorGPU.cpp index d7f701729a..6288973291 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorGPU.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorGPU.cpp @@ -75,25 +75,13 @@ CommandResult PaintingCommandExecutorGPU::set_font(Gfx::Font const&) return CommandResult::Continue; } -CommandResult PaintingCommandExecutorGPU::push_stacking_context(bool, float, Gfx::FloatRect const&, Gfx::FloatRect const&, Gfx::IntPoint const&) +CommandResult PaintingCommandExecutorGPU::push_stacking_context(float, bool, Gfx::IntRect const&, Gfx::IntPoint, CSS::ImageRendering, StackingContextTransform, Optional) { // FIXME return CommandResult::Continue; } -CommandResult PaintingCommandExecutorGPU::pop_stacking_context(bool, Gfx::Painter::ScalingMode) -{ - // FIXME - return CommandResult::Continue; -} - -CommandResult PaintingCommandExecutorGPU::push_stacking_context_with_mask(Gfx::IntRect const&) -{ - // FIXME - return CommandResult::Continue; -} - -CommandResult PaintingCommandExecutorGPU::pop_stacking_context_with_mask(Gfx::IntRect const&, RefPtr const&, Gfx::Bitmap::MaskKind, float) +CommandResult PaintingCommandExecutorGPU::pop_stacking_context() { // FIXME return CommandResult::Continue; diff --git a/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorGPU.h b/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorGPU.h index 07f1814932..cc737a36b4 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorGPU.h +++ b/Userland/Libraries/LibWeb/Painting/PaintingCommandExecutorGPU.h @@ -20,10 +20,8 @@ public: CommandResult set_clip_rect(Gfx::IntRect const& rect) override; CommandResult clear_clip_rect() override; CommandResult set_font(Gfx::Font const&) override; - CommandResult push_stacking_context(bool semitransparent_or_has_non_identity_transform, float opacity, Gfx::FloatRect const& source_rect, Gfx::FloatRect const& transformed_destination_rect, Gfx::IntPoint const& painter_location) override; - CommandResult pop_stacking_context(bool semitransparent_or_has_non_identity_transform, Gfx::Painter::ScalingMode scaling_mode) override; - CommandResult push_stacking_context_with_mask(Gfx::IntRect const&) override; - CommandResult pop_stacking_context_with_mask(Gfx::IntRect const&, RefPtr const& mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, float opacity) override; + CommandResult push_stacking_context(float opacity, bool, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering image_rendering, StackingContextTransform transform, Optional mask) override; + CommandResult pop_stacking_context() override; CommandResult paint_linear_gradient(Gfx::IntRect const&, Web::Painting::LinearGradientData const&) override; CommandResult paint_outer_box_shadow(PaintOuterBoxShadowParams const&) override; CommandResult paint_inner_box_shadow(PaintOuterBoxShadowParams const&) override; diff --git a/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp b/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp index f4acb1c9a9..bba00f79c6 100644 --- a/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp +++ b/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp @@ -266,33 +266,26 @@ void RecordingPainter::restore() 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 = state().translation.map(params.source_rect), - .transformed_destination_rect = state().translation.map(params.transformed_destination_rect), - .painter_location = state().translation.map(params.painter_location), - }); - - if (params.has_fixed_position) { - state().translation.set_translation(0, 0); - } - - if (params.semitransparent_or_has_non_identity_transform) { - m_state_stack.append(State()); - } + .is_fixed_position = params.is_fixed_position, + .source_paintable_rect = params.source_paintable_rect, + // No translations apply to fixed-position stacking contexts. + .post_transform_translation = params.is_fixed_position + ? Gfx::IntPoint {} + : state().translation.translation().to_rounded(), + .image_rendering = params.image_rendering, + .transform = { + .origin = params.transform.origin, + .matrix = params.transform.matrix, + }, + .mask = params.mask }); + m_state_stack.append(State()); } -void RecordingPainter::pop_stacking_context(PopStackingContextParams params) +void RecordingPainter::pop_stacking_context() { - push_command(PopStackingContext { - .semitransparent_or_has_non_identity_transform = params.semitransparent_or_has_non_identity_transform, - .scaling_mode = params.scaling_mode, - }); - - if (params.semitransparent_or_has_non_identity_transform) { - m_state_stack.take_last(); - } + push_command(PopStackingContext {}); + m_state_stack.take_last(); } void RecordingPainter::paint_progressbar(Gfx::IntRect frame_rect, Gfx::IntRect progress_rect, Palette palette, int min, int max, int value, StringView text) @@ -380,20 +373,6 @@ void RecordingPainter::fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect { bottom_left_radius, bottom_left_radius }); } -void RecordingPainter::push_stacking_context_with_mask(Gfx::IntRect paint_rect) -{ - push_command(PushStackingContextWithMask { .paint_rect = state().translation.map(paint_rect) }); -} - -void RecordingPainter::pop_stacking_context_with_mask(RefPtr mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, Gfx::IntRect paint_rect, float opacity) -{ - push_command(PopStackingContextWithMask { - .paint_rect = state().translation.map(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 { @@ -463,16 +442,10 @@ void RecordingPainter::execute(PaintingCommandExecutor& executor) return executor.set_font(command.font); }, [&](PushStackingContext const& command) { - return executor.push_stacking_context(command.semitransparent_or_has_non_identity_transform, command.opacity, command.source_rect, command.transformed_destination_rect, command.painter_location); + return executor.push_stacking_context(command.opacity, command.is_fixed_position, command.source_paintable_rect, command.post_transform_translation, command.image_rendering, command.transform, command.mask); }, - [&](PopStackingContext const& command) { - return executor.pop_stacking_context(command.semitransparent_or_has_non_identity_transform, command.scaling_mode); - }, - [&](PushStackingContextWithMask const& command) { - return executor.push_stacking_context_with_mask(command.paint_rect); - }, - [&](PopStackingContextWithMask const& command) { - return executor.pop_stacking_context_with_mask(command.paint_rect, command.mask_bitmap, command.mask_kind, command.opacity); + [&](PopStackingContext const&) { + return executor.pop_stacking_context(); }, [&](PaintLinearGradient const& command) { return executor.paint_linear_gradient(command.gradient_rect, command.linear_gradient_data); diff --git a/Userland/Libraries/LibWeb/Painting/RecordingPainter.h b/Userland/Libraries/LibWeb/Painting/RecordingPainter.h index 4baab328ed..ff18b89cb4 100644 --- a/Userland/Libraries/LibWeb/Painting/RecordingPainter.h +++ b/Userland/Libraries/LibWeb/Painting/RecordingPainter.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -86,31 +87,30 @@ struct SetFont { NonnullRefPtr font; }; -struct PushStackingContext { - bool semitransparent_or_has_non_identity_transform; - bool has_fixed_position; - float opacity; - Gfx::FloatRect source_rect; - Gfx::FloatRect transformed_destination_rect; - Gfx::IntPoint painter_location; +struct StackingContextTransform { + Gfx::FloatPoint origin; + Gfx::FloatMatrix4x4 matrix; }; -struct PopStackingContext { - bool semitransparent_or_has_non_identity_transform; - Gfx::Painter::ScalingMode scaling_mode; -}; - -struct PushStackingContextWithMask { - Gfx::IntRect paint_rect; -}; - -struct PopStackingContextWithMask { - Gfx::IntRect paint_rect; - RefPtr mask_bitmap; +struct StackingContextMask { + NonnullRefPtr mask_bitmap; Gfx::Bitmap::MaskKind mask_kind; - float opacity; }; +struct PushStackingContext { + float opacity; + bool is_fixed_position; + // The bounding box of the source paintable (pre-transform). + Gfx::IntRect source_paintable_rect; + // A translation to be applied after the stacking context has been transformed. + Gfx::IntPoint post_transform_translation; + CSS::ImageRendering image_rendering; + StackingContextTransform transform; + Optional mask = {}; +}; + +struct PopStackingContext { }; + struct PaintLinearGradient { Gfx::IntRect gradient_rect; LinearGradientData linear_gradient_data; @@ -312,8 +312,6 @@ using PaintingCommand = Variant< SetFont, PushStackingContext, PopStackingContext, - PushStackingContextWithMask, - PopStackingContextWithMask, PaintLinearGradient, PaintRadialGradient, PaintConicGradient, @@ -348,10 +346,8 @@ public: virtual CommandResult set_clip_rect(Gfx::IntRect const& rect) = 0; virtual CommandResult clear_clip_rect() = 0; virtual CommandResult set_font(Gfx::Font const& font) = 0; - virtual CommandResult push_stacking_context(bool semitransparent_or_has_non_identity_transform, float opacity, Gfx::FloatRect const& source_rect, Gfx::FloatRect const& transformed_destination_rect, Gfx::IntPoint const& painter_location) = 0; - virtual CommandResult pop_stacking_context(bool semitransparent_or_has_non_identity_transform, Gfx::Painter::ScalingMode scaling_mode) = 0; - virtual CommandResult push_stacking_context_with_mask(Gfx::IntRect const& paint_rect) = 0; - virtual CommandResult pop_stacking_context_with_mask(Gfx::IntRect const& paint_rect, RefPtr const& mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, float opacity) = 0; + virtual CommandResult push_stacking_context(float opacity, bool is_fixed_position, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering image_rendering, StackingContextTransform transform, Optional mask) = 0; + virtual CommandResult pop_stacking_context() = 0; virtual CommandResult paint_linear_gradient(Gfx::IntRect const&, LinearGradientData const&) = 0; virtual CommandResult paint_radial_gradient(Gfx::IntRect const& rect, RadialGradientData const&, Gfx::IntPoint const& center, Gfx::IntSize const& size) = 0; virtual CommandResult paint_conic_gradient(Gfx::IntRect const& rect, ConicGradientData const&, Gfx::IntPoint const& position) = 0; @@ -452,23 +448,15 @@ public: 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; - Gfx::IntPoint painter_location; + bool is_fixed_position; + Gfx::IntRect source_paintable_rect; + CSS::ImageRendering image_rendering; + StackingContextTransform transform; + Optional mask = {}; }; 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(Gfx::IntRect paint_rect); - void pop_stacking_context_with_mask(RefPtr mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, Gfx::IntRect paint_rect, float opacity); + void pop_stacking_context(); void sample_under_corners(NonnullRefPtr corner_clipper); void blit_corner_clipping(NonnullRefPtr corner_clipper); diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index f5642eb33a..12e37bdf66 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -282,72 +282,52 @@ Gfx::FloatMatrix4x4 StackingContext::combine_transformations(Vector(), + .image_rendering = paintable_box().computed_values().image_rendering(), + .transform = { + .origin = transform_origin().scaled(to_device_pixels_scale), + .matrix = matrix_with_scaled_translation(transform_matrix(), to_device_pixels_scale), + }, + }; + if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) { - // TODO: Support masks and CSS transforms at the same time. - // Note: Currently only SVG masking is implemented (which does not use CSS transforms anyway). if (masking_area->is_empty()) return; - auto paint_rect = context.enclosing_device_rect(*masking_area); - context.painter().push_stacking_context_with_mask(paint_rect.to_type()); - 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.to_type(), opacity); - return; + push_stacking_context_params.source_paintable_rect = context.enclosing_device_rect(*masking_area).to_type(); + push_stacking_context_params.mask = StackingContextMask { + .mask_bitmap = mask_bitmap.release_nonnull(), + .mask_kind = *paintable_box().get_mask_type() + }; } - auto affine_transform = affine_transform_matrix(); - 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()).to_type() - }; - - 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()) { - 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 { - context.painter().translate(affine_transform.translation().to_rounded()); - context.painter().push_stacking_context(push_stacking_context_params); - paint_internal(context); - context.painter().pop_stacking_context(pop_stacking_context_params); - } + context.painter().push_stacking_context(push_stacking_context_params); + paint_internal(context); + context.painter().pop_stacking_context(); } Gfx::FloatPoint StackingContext::compute_transform_origin() const