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