1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 20:07:36 +00:00

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<Gfx::Bitmap> 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<StackingContextMask> mask);

pop_stacking_context()
```

And moves the implementation details into the executor, this should
allow future backends to implement stacking contexts without these
limitations.
This commit is contained in:
MacDue 2023-11-18 14:23:59 +00:00 committed by Alexander Kalenik
parent 848b0d9c81
commit 4e04f81626
7 changed files with 189 additions and 253 deletions

View file

@ -6,6 +6,7 @@
#include <LibGfx/Filters/StackBlurFilter.h> #include <LibGfx/Filters/StackBlurFilter.h>
#include <LibGfx/StylePainter.h> #include <LibGfx/StylePainter.h>
#include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/Painting/BorderRadiusCornerClipper.h> #include <LibWeb/Painting/BorderRadiusCornerClipper.h>
#include <LibWeb/Painting/FilterPainting.h> #include <LibWeb/Painting/FilterPainting.h>
#include <LibWeb/Painting/PaintingCommandExecutorCPU.h> #include <LibWeb/Painting/PaintingCommandExecutorCPU.h>
@ -17,7 +18,10 @@ namespace Web::Painting {
PaintingCommandExecutorCPU::PaintingCommandExecutorCPU(Gfx::Bitmap& bitmap) PaintingCommandExecutorCPU::PaintingCommandExecutorCPU(Gfx::Bitmap& bitmap)
: m_target_bitmap(bitmap) : m_target_bitmap(bitmap)
{ {
stacking_contexts.append({ Gfx::Painter(bitmap), {}, 1.0f }); stacking_contexts.append({ .painter = AK::make<Gfx::Painter>(bitmap),
.opacity = 1.0f,
.destination = {},
.scaling_mode = {} });
} }
CommandResult PaintingCommandExecutorCPU::draw_glyph_run(Vector<Gfx::DrawGlyphOrEmoji> const& glyph_run, Color const& color) CommandResult PaintingCommandExecutorCPU::draw_glyph_run(Vector<Gfx::DrawGlyphOrEmoji> const& glyph_run, Color const& color)
@ -70,8 +74,7 @@ CommandResult PaintingCommandExecutorCPU::set_clip_rect(Gfx::IntRect const& rect
CommandResult PaintingCommandExecutorCPU::clear_clip_rect() CommandResult PaintingCommandExecutorCPU::clear_clip_rect()
{ {
auto& painter = this->painter(); painter().clear_clip_rect();
painter.clear_clip_rect();
return CommandResult::Continue; return CommandResult::Continue;
} }
@ -82,10 +85,49 @@ CommandResult PaintingCommandExecutorCPU::set_font(Gfx::Font const& font)
return CommandResult::Continue; 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<StackingContextMask> mask)
{ {
auto& painter = this->painter(); painter().save();
if (semitransparent_or_has_non_identity_transform) { 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 = AK::make<Gfx::Painter>(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<int>() + 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<float>().translated(-transform.origin);
auto transformed_destination_rect = affine_transform.map(source_rect).translated(transform.origin);
auto destination_rect = transformed_destination_rect.to_rounded<int>(); auto destination_rect = transformed_destination_rect.to_rounded<int>();
// FIXME: We should find a way to scale the paintable, rather than paint into a separate bitmap, // FIXME: We should find a way to scale the paintable, rather than paint into a separate bitmap,
@ -96,7 +138,7 @@ CommandResult PaintingCommandExecutorCPU::push_stacking_context(bool semitranspa
Gfx::FloatPoint destination_clipped_fixup {}; Gfx::FloatPoint destination_clipped_fixup {};
auto try_get_scaled_destination_bitmap = [&]() -> ErrorOr<NonnullRefPtr<Gfx::Bitmap>> { auto try_get_scaled_destination_bitmap = [&]() -> ErrorOr<NonnullRefPtr<Gfx::Bitmap>> {
Gfx::IntRect actual_destination_rect; Gfx::IntRect actual_destination_rect;
auto bitmap = TRY(painter.get_region_bitmap(destination_rect, Gfx::BitmapFormat::BGRA8888, 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. // 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_clipped_fixup = Gfx::FloatPoint { destination_rect.location() - actual_destination_rect.location() };
destination_rect = actual_destination_rect; destination_rect = actual_destination_rect;
@ -119,70 +161,36 @@ CommandResult PaintingCommandExecutorCPU::push_stacking_context(bool semitranspa
// if we run out of memory. // if we run out of memory.
return CommandResult::SkipStackingContext; 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<int>());
auto bitmap = bitmap_or_error.release_value();
stacking_contexts.append(StackingContext { stacking_contexts.append(StackingContext {
.painter = stacking_context_painter, .painter = AK::make<Gfx::Painter>(bitmap),
.destination = destination_rect,
.opacity = opacity, .opacity = opacity,
}); .destination = destination_rect.translated(post_transform_translation),
} else { .scaling_mode = CSS::to_gfx_scaling_mode(image_rendering, destination_rect, destination_rect) });
painter.save(); painter().translate(-source_paintable_rect.location() + destination_clipped_fixup.to_type<int>());
}
return CommandResult::Continue; 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) { ScopeGuard restore_painter = [&] {
painter().restore();
};
auto stacking_context = stacking_contexts.take_last(); auto stacking_context = stacking_contexts.take_last();
auto bitmap = stacking_context.painter.target(); // 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; auto destination_rect = stacking_context.destination;
if (destination_rect.size() == bitmap->size()) { if (destination_rect.size() == bitmap->size()) {
painter().blit(destination_rect.location(), *bitmap, bitmap->rect(), stacking_context.opacity); painter().blit(destination_rect.location(), *bitmap, bitmap->rect(), stacking_context.opacity);
} else { } 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<Gfx::Bitmap> 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; return CommandResult::Continue;
} }

View file

@ -6,6 +6,7 @@
#pragma once #pragma once
#include <AK/MaybeOwned.h>
#include <LibWeb/Painting/RecordingPainter.h> #include <LibWeb/Painting/RecordingPainter.h>
namespace Web::Painting { namespace Web::Painting {
@ -19,10 +20,8 @@ public:
CommandResult set_clip_rect(Gfx::IntRect const& rect) override; CommandResult set_clip_rect(Gfx::IntRect const& rect) override;
CommandResult clear_clip_rect() override; CommandResult clear_clip_rect() override;
CommandResult set_font(Gfx::Font const&) 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 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<StackingContextMask> mask) override;
CommandResult pop_stacking_context(bool semitransparent_or_has_non_identity_transform, Gfx::Painter::ScalingMode scaling_mode) override; CommandResult pop_stacking_context() override;
CommandResult push_stacking_context_with_mask(Gfx::IntRect const&) override;
CommandResult pop_stacking_context_with_mask(Gfx::IntRect const&, RefPtr<Gfx::Bitmap> const& mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, float opacity) override;
CommandResult paint_linear_gradient(Gfx::IntRect const&, Web::Painting::LinearGradientData const&) override; CommandResult paint_linear_gradient(Gfx::IntRect const&, Web::Painting::LinearGradientData const&) override;
CommandResult paint_outer_box_shadow(PaintOuterBoxShadowParams const&) override; CommandResult paint_outer_box_shadow(PaintOuterBoxShadowParams const&) override;
CommandResult paint_inner_box_shadow(PaintOuterBoxShadowParams const&) override; CommandResult paint_inner_box_shadow(PaintOuterBoxShadowParams const&) override;
@ -57,13 +56,15 @@ private:
Gfx::Bitmap& m_target_bitmap; Gfx::Bitmap& m_target_bitmap;
struct StackingContext { struct StackingContext {
Gfx::Painter painter; MaybeOwned<Gfx::Painter> painter;
Gfx::IntRect destination;
float opacity; float opacity;
Gfx::IntRect destination;
Gfx::Painter::ScalingMode scaling_mode;
Optional<StackingContextMask> mask = {};
}; };
[[nodiscard]] Gfx::Painter const& painter() const { 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; } [[nodiscard]] Gfx::Painter& painter() { return *stacking_contexts.last().painter; }
Vector<StackingContext> stacking_contexts; Vector<StackingContext> stacking_contexts;
}; };

View file

@ -75,25 +75,13 @@ CommandResult PaintingCommandExecutorGPU::set_font(Gfx::Font const&)
return CommandResult::Continue; 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<StackingContextMask>)
{ {
// FIXME // FIXME
return CommandResult::Continue; return CommandResult::Continue;
} }
CommandResult PaintingCommandExecutorGPU::pop_stacking_context(bool, Gfx::Painter::ScalingMode) CommandResult PaintingCommandExecutorGPU::pop_stacking_context()
{
// 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<Gfx::Bitmap> const&, Gfx::Bitmap::MaskKind, float)
{ {
// FIXME // FIXME
return CommandResult::Continue; return CommandResult::Continue;

View file

@ -20,10 +20,8 @@ public:
CommandResult set_clip_rect(Gfx::IntRect const& rect) override; CommandResult set_clip_rect(Gfx::IntRect const& rect) override;
CommandResult clear_clip_rect() override; CommandResult clear_clip_rect() override;
CommandResult set_font(Gfx::Font const&) 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 push_stacking_context(float opacity, bool, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering image_rendering, StackingContextTransform transform, Optional<StackingContextMask> mask) override;
CommandResult pop_stacking_context(bool semitransparent_or_has_non_identity_transform, Gfx::Painter::ScalingMode scaling_mode) override; CommandResult pop_stacking_context() override;
CommandResult push_stacking_context_with_mask(Gfx::IntRect const&) override;
CommandResult pop_stacking_context_with_mask(Gfx::IntRect const&, RefPtr<Gfx::Bitmap> const& mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, float opacity) override;
CommandResult paint_linear_gradient(Gfx::IntRect const&, Web::Painting::LinearGradientData const&) override; CommandResult paint_linear_gradient(Gfx::IntRect const&, Web::Painting::LinearGradientData const&) override;
CommandResult paint_outer_box_shadow(PaintOuterBoxShadowParams const&) override; CommandResult paint_outer_box_shadow(PaintOuterBoxShadowParams const&) override;
CommandResult paint_inner_box_shadow(PaintOuterBoxShadowParams const&) override; CommandResult paint_inner_box_shadow(PaintOuterBoxShadowParams const&) override;

View file

@ -266,33 +266,26 @@ void RecordingPainter::restore()
void RecordingPainter::push_stacking_context(PushStackingContextParams params) void RecordingPainter::push_stacking_context(PushStackingContextParams params)
{ {
push_command(PushStackingContext { 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, .opacity = params.opacity,
.source_rect = state().translation.map(params.source_rect), .is_fixed_position = params.is_fixed_position,
.transformed_destination_rect = state().translation.map(params.transformed_destination_rect), .source_paintable_rect = params.source_paintable_rect,
.painter_location = state().translation.map(params.painter_location), // No translations apply to fixed-position stacking contexts.
}); .post_transform_translation = params.is_fixed_position
? Gfx::IntPoint {}
if (params.has_fixed_position) { : state().translation.translation().to_rounded<int>(),
state().translation.set_translation(0, 0); .image_rendering = params.image_rendering,
} .transform = {
.origin = params.transform.origin,
if (params.semitransparent_or_has_non_identity_transform) { .matrix = params.transform.matrix,
},
.mask = params.mask });
m_state_stack.append(State()); m_state_stack.append(State());
}
} }
void RecordingPainter::pop_stacking_context(PopStackingContextParams params) void RecordingPainter::pop_stacking_context()
{ {
push_command(PopStackingContext { 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(); 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) 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 }); { 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<Gfx::Bitmap> 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) void RecordingPainter::draw_triangle_wave(Gfx::IntPoint a_p1, Gfx::IntPoint a_p2, Color color, int amplitude, int thickness = 1)
{ {
push_command(DrawTriangleWave { push_command(DrawTriangleWave {
@ -463,16 +442,10 @@ void RecordingPainter::execute(PaintingCommandExecutor& executor)
return executor.set_font(command.font); return executor.set_font(command.font);
}, },
[&](PushStackingContext const& command) { [&](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) { [&](PopStackingContext const&) {
return executor.pop_stacking_context(command.semitransparent_or_has_non_identity_transform, command.scaling_mode); return executor.pop_stacking_context();
},
[&](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);
}, },
[&](PaintLinearGradient const& command) { [&](PaintLinearGradient const& command) {
return executor.paint_linear_gradient(command.gradient_rect, command.linear_gradient_data); return executor.paint_linear_gradient(command.gradient_rect, command.linear_gradient_data);

View file

@ -27,6 +27,7 @@
#include <LibGfx/TextElision.h> #include <LibGfx/TextElision.h>
#include <LibGfx/TextLayout.h> #include <LibGfx/TextLayout.h>
#include <LibGfx/TextWrapping.h> #include <LibGfx/TextWrapping.h>
#include <LibWeb/CSS/Enums.h>
#include <LibWeb/Painting/BorderRadiiData.h> #include <LibWeb/Painting/BorderRadiiData.h>
#include <LibWeb/Painting/BorderRadiusCornerClipper.h> #include <LibWeb/Painting/BorderRadiusCornerClipper.h>
#include <LibWeb/Painting/GradientData.h> #include <LibWeb/Painting/GradientData.h>
@ -86,31 +87,30 @@ struct SetFont {
NonnullRefPtr<Gfx::Font> font; NonnullRefPtr<Gfx::Font> font;
}; };
struct PushStackingContext { struct StackingContextTransform {
bool semitransparent_or_has_non_identity_transform; Gfx::FloatPoint origin;
bool has_fixed_position; Gfx::FloatMatrix4x4 matrix;
float opacity;
Gfx::FloatRect source_rect;
Gfx::FloatRect transformed_destination_rect;
Gfx::IntPoint painter_location;
}; };
struct PopStackingContext { struct StackingContextMask {
bool semitransparent_or_has_non_identity_transform; NonnullRefPtr<Gfx::Bitmap> mask_bitmap;
Gfx::Painter::ScalingMode scaling_mode;
};
struct PushStackingContextWithMask {
Gfx::IntRect paint_rect;
};
struct PopStackingContextWithMask {
Gfx::IntRect paint_rect;
RefPtr<Gfx::Bitmap> mask_bitmap;
Gfx::Bitmap::MaskKind mask_kind; 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<StackingContextMask> mask = {};
};
struct PopStackingContext { };
struct PaintLinearGradient { struct PaintLinearGradient {
Gfx::IntRect gradient_rect; Gfx::IntRect gradient_rect;
LinearGradientData linear_gradient_data; LinearGradientData linear_gradient_data;
@ -312,8 +312,6 @@ using PaintingCommand = Variant<
SetFont, SetFont,
PushStackingContext, PushStackingContext,
PopStackingContext, PopStackingContext,
PushStackingContextWithMask,
PopStackingContextWithMask,
PaintLinearGradient, PaintLinearGradient,
PaintRadialGradient, PaintRadialGradient,
PaintConicGradient, PaintConicGradient,
@ -348,10 +346,8 @@ public:
virtual CommandResult set_clip_rect(Gfx::IntRect const& rect) = 0; virtual CommandResult set_clip_rect(Gfx::IntRect const& rect) = 0;
virtual CommandResult clear_clip_rect() = 0; virtual CommandResult clear_clip_rect() = 0;
virtual CommandResult set_font(Gfx::Font const& font) = 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 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<StackingContextMask> mask) = 0;
virtual CommandResult pop_stacking_context(bool semitransparent_or_has_non_identity_transform, Gfx::Painter::ScalingMode scaling_mode) = 0; virtual CommandResult pop_stacking_context() = 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<Gfx::Bitmap> const& mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, float opacity) = 0;
virtual CommandResult paint_linear_gradient(Gfx::IntRect const&, LinearGradientData const&) = 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_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; virtual CommandResult paint_conic_gradient(Gfx::IntRect const& rect, ConicGradientData const&, Gfx::IntPoint const& position) = 0;
@ -452,23 +448,15 @@ public:
void restore(); void restore();
struct PushStackingContextParams { struct PushStackingContextParams {
bool semitransparent_or_has_non_identity_transform;
bool has_fixed_position;
float opacity; float opacity;
Gfx::FloatRect source_rect; bool is_fixed_position;
Gfx::FloatRect transformed_destination_rect; Gfx::IntRect source_paintable_rect;
Gfx::IntPoint painter_location; CSS::ImageRendering image_rendering;
StackingContextTransform transform;
Optional<StackingContextMask> mask = {};
}; };
void push_stacking_context(PushStackingContextParams params); void push_stacking_context(PushStackingContextParams params);
void pop_stacking_context();
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<Gfx::Bitmap> mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, Gfx::IntRect paint_rect, float opacity);
void sample_under_corners(NonnullRefPtr<BorderRadiusCornerClipper> corner_clipper); void sample_under_corners(NonnullRefPtr<BorderRadiusCornerClipper> corner_clipper);
void blit_corner_clipping(NonnullRefPtr<BorderRadiusCornerClipper> corner_clipper); void blit_corner_clipping(NonnullRefPtr<BorderRadiusCornerClipper> corner_clipper);

View file

@ -282,72 +282,52 @@ Gfx::FloatMatrix4x4 StackingContext::combine_transformations(Vector<CSS::Transfo
// Use the whole matrix when we get better transformation support in LibGfx or use LibGL for drawing the bitmap // Use the whole matrix when we get better transformation support in LibGfx or use LibGL for drawing the bitmap
Gfx::AffineTransform StackingContext::affine_transform_matrix() const Gfx::AffineTransform StackingContext::affine_transform_matrix() const
{ {
auto* m = m_transform.elements(); return Gfx::extract_2d_affine_transform(m_transform);
return Gfx::AffineTransform(m[0][0], m[1][0], m[0][1], m[1][1], m[0][3], m[1][3]); }
static Gfx::FloatMatrix4x4 matrix_with_scaled_translation(Gfx::FloatMatrix4x4 matrix, float scale)
{
auto* m = matrix.elements();
m[0][3] *= scale;
m[1][3] *= scale;
m[2][3] *= scale;
return matrix;
} }
void StackingContext::paint(PaintContext& context) const void StackingContext::paint(PaintContext& context) const
{ {
RecordingPainterStateSaver saver(context.painter());
auto opacity = paintable_box().computed_values().opacity(); auto opacity = paintable_box().computed_values().opacity();
if (opacity == 0.0f) if (opacity == 0.0f)
return; return;
RecordingPainterStateSaver saver(context.painter());
auto to_device_pixels_scale = float(context.device_pixels_per_css_pixel());
RecordingPainter::PushStackingContextParams push_stacking_context_params {
.opacity = opacity,
.is_fixed_position = paintable_box().is_fixed_position(),
.source_paintable_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type<int>(),
.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()) { 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()) if (masking_area->is_empty())
return; return;
auto paint_rect = context.enclosing_device_rect(*masking_area);
context.painter().push_stacking_context_with_mask(paint_rect.to_type<int>());
paint_internal(context);
auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area); auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area);
auto mask_type = paintable_box().get_mask_type(); push_stacking_context_params.source_paintable_rect = context.enclosing_device_rect(*masking_area).to_type<int>();
context.painter().pop_stacking_context_with_mask(mask_bitmap, *mask_type, paint_rect.to_type<int>(), opacity); push_stacking_context_params.mask = StackingContextMask {
return; .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<CSSPixels>()).to_type<int>().to_type<float>();
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<int>().to_type<float>().translated(-transform_origin);
auto transformed_destination_rect = affine_transform.map(source_rect).translated(transform_origin);
auto destination_rect = transformed_destination_rect.to_rounded<int>();
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<int>()
};
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); context.painter().push_stacking_context(push_stacking_context_params);
paint_internal(context); paint_internal(context);
context.painter().pop_stacking_context(pop_stacking_context_params); context.painter().pop_stacking_context();
} else {
context.painter().translate(affine_transform.translation().to_rounded<int>());
context.painter().push_stacking_context(push_stacking_context_params);
paint_internal(context);
context.painter().pop_stacking_context(pop_stacking_context_params);
}
} }
Gfx::FloatPoint StackingContext::compute_transform_origin() const Gfx::FloatPoint StackingContext::compute_transform_origin() const