LibWeb: Let image paint callers own clipping
DecodedImageData::paint() used to take both a destination and a clip rectangle even though most callers passed the same value. SVG image painting used that API to wrap every nested SVG display list in save/add-clip/restore, which put an unbounded command in front of the bounded nested-list command and made offscreen SVG image content harder to cull. Move clipping to ImagePaintable, where the object-fit destination can be compared with the replaced element box. CSS image and marker painting continue to draw into their destination rect, while repeated background images keep their explicit tile clip. The scaled decoded image display-list command now stores only its destination rect and uses that as its bounds; playback still clips decoded images to that rect so bitmap rendering stays unchanged.
This commit is contained in:
parent
cdb8762c8f
commit
852c7a10f3
19 changed files with 88 additions and 46 deletions
|
|
@ -244,7 +244,7 @@ void ImageStyleValue::paint(DisplayListRecordingContext& context, DevicePixelRec
|
|||
auto dest_int_rect = dest_rect.to_type<int>();
|
||||
auto rect = image_data->frame_rect(m_current_frame_index).value_or(dest_int_rect);
|
||||
auto scaling_mode = to_gfx_scaling_mode(image_rendering, rect.size(), dest_int_rect.size());
|
||||
image_data->paint(context, m_current_frame_index, dest_int_rect, dest_int_rect, scaling_mode);
|
||||
image_data->paint(context, m_current_frame_index, dest_int_rect, scaling_mode);
|
||||
}
|
||||
|
||||
Optional<Gfx::DecodedImageFrame> ImageStyleValue::current_frame(DevicePixelRect const& dest_rect) const
|
||||
|
|
|
|||
|
|
@ -179,12 +179,12 @@ Optional<Gfx::IntRect> AnimatedDecodedImageData::frame_rect(size_t) const
|
|||
return Gfx::IntRect { {}, m_size };
|
||||
}
|
||||
|
||||
void AnimatedDecodedImageData::paint(DisplayListRecordingContext& context, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const
|
||||
void AnimatedDecodedImageData::paint(DisplayListRecordingContext& context, size_t frame_index, Gfx::IntRect dst_rect, Gfx::ScalingMode scaling_mode) const
|
||||
{
|
||||
auto decoded_frame = frame(frame_index);
|
||||
if (!decoded_frame.has_value())
|
||||
return;
|
||||
context.display_list_recorder().draw_scaled_decoded_image_frame(dst_rect, clip_rect, *decoded_frame, scaling_mode);
|
||||
context.display_list_recorder().draw_scaled_decoded_image_frame(dst_rect, *decoded_frame, scaling_mode);
|
||||
}
|
||||
|
||||
void AnimatedDecodedImageData::receive_frames(Vector<NonnullRefPtr<Gfx::Bitmap>> bitmaps, u32 start_frame_index)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public:
|
|||
virtual Optional<CSSPixelFraction> intrinsic_aspect_ratio() const override;
|
||||
|
||||
virtual Optional<Gfx::IntRect> frame_rect(size_t frame_index) const override;
|
||||
virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode) const override;
|
||||
virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, Gfx::ScalingMode) const override;
|
||||
|
||||
virtual size_t notify_frame_advanced(size_t caller_frame_index) override;
|
||||
|
||||
|
|
|
|||
|
|
@ -72,9 +72,9 @@ Optional<Gfx::IntRect> BitmapDecodedImageData::frame_rect(size_t frame_index) co
|
|||
return m_frames[frame_index].frame.rect();
|
||||
}
|
||||
|
||||
void BitmapDecodedImageData::paint(DisplayListRecordingContext& context, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const
|
||||
void BitmapDecodedImageData::paint(DisplayListRecordingContext& context, size_t frame_index, Gfx::IntRect dst_rect, Gfx::ScalingMode scaling_mode) const
|
||||
{
|
||||
context.display_list_recorder().draw_scaled_decoded_image_frame(dst_rect, clip_rect, m_frames[frame_index].frame, scaling_mode);
|
||||
context.display_list_recorder().draw_scaled_decoded_image_frame(dst_rect, m_frames[frame_index].frame, scaling_mode);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public:
|
|||
virtual Optional<CSSPixelFraction> intrinsic_aspect_ratio() const override;
|
||||
|
||||
virtual Optional<Gfx::IntRect> frame_rect(size_t frame_index) const override;
|
||||
virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const override;
|
||||
virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, Gfx::ScalingMode scaling_mode) const override;
|
||||
|
||||
private:
|
||||
BitmapDecodedImageData(Vector<Frame>&&, size_t loop_count, bool animated);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public:
|
|||
virtual ~DecodedImageData();
|
||||
|
||||
virtual Optional<Gfx::IntRect> frame_rect([[maybe_unused]] size_t frame_index) const = 0;
|
||||
virtual void paint([[maybe_unused]] DisplayListRecordingContext&, [[maybe_unused]] size_t frame_index, [[maybe_unused]] Gfx::IntRect dst_rect, [[maybe_unused]] Gfx::IntRect clip_rect, [[maybe_unused]] Gfx::ScalingMode scaling_mode) const = 0;
|
||||
virtual void paint([[maybe_unused]] DisplayListRecordingContext&, [[maybe_unused]] size_t frame_index, [[maybe_unused]] Gfx::IntRect dst_rect, [[maybe_unused]] Gfx::ScalingMode scaling_mode) const = 0;
|
||||
|
||||
virtual Optional<Gfx::DecodedImageFrame> frame(size_t frame_index, Gfx::IntSize = {}) const = 0;
|
||||
virtual int frame_duration(size_t frame_index) const = 0;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ void FillRect::dump(StringBuilder& builder) const
|
|||
|
||||
void DrawScaledDecodedImageFrame::dump(StringBuilder& builder) const
|
||||
{
|
||||
builder.appendff(" dst_rect={} clip_rect={}", dst_rect, clip_rect);
|
||||
builder.appendff(" dst_rect={}", dst_rect);
|
||||
}
|
||||
|
||||
void DrawRepeatedDecodedImageFrame::dump(StringBuilder& builder) const
|
||||
|
|
|
|||
|
|
@ -145,11 +145,10 @@ struct DrawScaledDecodedImageFrame {
|
|||
static constexpr DisplayListCommandType command_type = DisplayListCommandType::DrawScaledDecodedImageFrame;
|
||||
|
||||
Gfx::IntRect dst_rect;
|
||||
Gfx::IntRect clip_rect;
|
||||
ImageFrameResourceId frame_id;
|
||||
Gfx::ScalingMode scaling_mode;
|
||||
|
||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return clip_rect; }
|
||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; }
|
||||
void dump(StringBuilder&) const;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -276,12 +276,11 @@ void DisplayListPlayerSkia::draw_scaled_decoded_image_frame(DrawScaledDecodedIma
|
|||
return;
|
||||
|
||||
auto dst_rect = to_skia_rect(command.dst_rect);
|
||||
auto clip_rect = to_skia_rect(command.clip_rect);
|
||||
auto& canvas = surface().canvas();
|
||||
SkPaint paint;
|
||||
paint.setAntiAlias(true);
|
||||
canvas.save();
|
||||
canvas.clipRect(clip_rect, true);
|
||||
canvas.clipRect(dst_rect, true);
|
||||
canvas.drawImageRect(image.get(), dst_rect, to_skia_sampling_options(command.scaling_mode), &paint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
|
@ -858,6 +857,7 @@ void DisplayListPlayerSkia::add_rounded_rect_clip(AddRoundedRectClip const& comm
|
|||
void DisplayListPlayerSkia::paint_nested_display_list(PaintNestedDisplayList const& command)
|
||||
{
|
||||
auto& canvas = surface().canvas();
|
||||
canvas.save();
|
||||
canvas.translate(command.rect.x(), command.rect.y());
|
||||
ScrollStateSnapshot scroll_state_snapshot;
|
||||
auto const& nested_display_list = resource_storage().display_list_resource(command.display_list_id);
|
||||
|
|
@ -866,6 +866,7 @@ void DisplayListPlayerSkia::paint_nested_display_list(PaintNestedDisplayList con
|
|||
nested_display_list.visual_context_tree,
|
||||
scroll_state_snapshot,
|
||||
nested_display_list.display_list->command_bytes());
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
void DisplayListPlayerSkia::compositor_scroll_node(CompositorScrollNode const&)
|
||||
|
|
|
|||
|
|
@ -534,13 +534,12 @@ void DisplayListRecorder::draw_video_frame(Gfx::IntRect const& dst_rect, VideoFr
|
|||
});
|
||||
}
|
||||
|
||||
void DisplayListRecorder::draw_scaled_decoded_image_frame(Gfx::IntRect const& dst_rect, Gfx::IntRect const& clip_rect, Gfx::DecodedImageFrame frame, Gfx::ScalingMode scaling_mode)
|
||||
void DisplayListRecorder::draw_scaled_decoded_image_frame(Gfx::IntRect const& dst_rect, Gfx::DecodedImageFrame frame, Gfx::ScalingMode scaling_mode)
|
||||
{
|
||||
if (dst_rect.is_empty())
|
||||
return;
|
||||
append_command(DrawScaledDecodedImageFrame {
|
||||
.dst_rect = dst_rect,
|
||||
.clip_rect = clip_rect,
|
||||
.frame_id = resource_storage().add_image_frame(frame),
|
||||
.scaling_mode = scaling_mode,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public:
|
|||
|
||||
void draw_rect(Gfx::IntRect const& rect, Color color, bool rough = false);
|
||||
|
||||
void draw_scaled_decoded_image_frame(Gfx::IntRect const& dst_rect, Gfx::IntRect const& clip_rect, Gfx::DecodedImageFrame frame, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor);
|
||||
void draw_scaled_decoded_image_frame(Gfx::IntRect const& dst_rect, Gfx::DecodedImageFrame frame, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor);
|
||||
void draw_compositor_surface(Gfx::IntRect const& dst_rect, CompositorSurfaceId, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor);
|
||||
void draw_video_frame(Gfx::IntRect const& dst_rect, VideoFrameResourceId, RefPtr<Media::VideoFrame const>, Gfx::ScalingMode scaling_mode = Gfx::ScalingMode::NearestNeighbor);
|
||||
|
||||
|
|
|
|||
|
|
@ -76,8 +76,16 @@ void ImagePaintable::paint(DisplayListRecordingContext& context, PaintPhase phas
|
|||
// https://drafts.csswg.org/css-images/#the-object-fit
|
||||
auto object_fit = m_is_svg_image ? CSS::ObjectFit::Contain : computed_values().object_fit();
|
||||
auto draw_rect = get_replaced_box_painting_area(*this, context, object_fit, bitmap_rect.size());
|
||||
if (!draw_rect.is_empty())
|
||||
decoded_image_data->paint(context, m_image_provider.current_frame_index(), draw_rect, image_int_rect_device_pixels, scaling_mode);
|
||||
if (!draw_rect.is_empty()) {
|
||||
auto draw_rect_needs_clip = !image_int_rect_device_pixels.contains(draw_rect);
|
||||
if (draw_rect_needs_clip) {
|
||||
context.display_list_recorder().save();
|
||||
context.display_list_recorder().add_clip_rect(image_int_rect_device_pixels);
|
||||
}
|
||||
decoded_image_data->paint(context, m_image_provider.current_frame_index(), draw_rect, scaling_mode);
|
||||
if (draw_rect_needs_clip)
|
||||
context.display_list_recorder().restore();
|
||||
}
|
||||
}
|
||||
|
||||
if (selection_state() != SelectionState::None) {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ void VideoPaintable::paint(DisplayListRecordingContext& context, PaintPhase phas
|
|||
if (dst_rect.is_empty())
|
||||
return;
|
||||
auto scaling_mode = to_gfx_scaling_mode(computed_values().image_rendering(), frame.size(), dst_rect.size());
|
||||
context.display_list_recorder().draw_scaled_decoded_image_frame(dst_rect, dst_rect, move(frame), scaling_mode);
|
||||
context.display_list_recorder().draw_scaled_decoded_image_frame(dst_rect, move(frame), scaling_mode);
|
||||
};
|
||||
|
||||
auto paint_video_frame = [&]() {
|
||||
|
|
|
|||
|
|
@ -310,16 +310,13 @@ RefPtr<Gfx::PaintingSurface> SVGDecodedImageData::surface(size_t, Gfx::IntSize s
|
|||
return render_to_surface(size);
|
||||
}
|
||||
|
||||
void SVGDecodedImageData::paint(DisplayListRecordingContext& context, size_t, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode) const
|
||||
void SVGDecodedImageData::paint(DisplayListRecordingContext& context, size_t, Gfx::IntRect dst_rect, Gfx::ScalingMode) const
|
||||
{
|
||||
auto display_list = record_display_list(dst_rect.size(), context.display_list_recorder().resource_storage());
|
||||
if (!display_list.has_value())
|
||||
return;
|
||||
|
||||
context.display_list_recorder().save();
|
||||
context.display_list_recorder().add_clip_rect(clip_rect);
|
||||
context.display_list_recorder().paint_nested_display_list(*display_list, dst_rect);
|
||||
context.display_list_recorder().restore();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public:
|
|||
virtual size_t external_memory_size() const override;
|
||||
|
||||
virtual Optional<Gfx::IntRect> frame_rect(size_t frame_index) const override;
|
||||
virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const override;
|
||||
virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, Gfx::ScalingMode scaling_mode) const override;
|
||||
|
||||
private:
|
||||
SVGDecodedImageData(GC::Ref<Page>, GC::Ref<SVGPageClient>, GC::Ref<DOM::Document>, GC::Ref<SVG::SVGSVGElement>);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<!doctype html>
|
||||
<meta name="fuzzy" content="maxDifference=0-3;totalPixels=0-568">
|
||||
<meta name="fuzzy" content="maxDifference=0-13;totalPixels=0-568">
|
||||
<style>
|
||||
body {
|
||||
background-color: white;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
AccumulatedVisualContext Tree:
|
||||
[0] transform=[1,0,0,1,0,0] origin=(0,0) (ViewportPaintable(Viewport<#document>))
|
||||
[1] scroll_frame_id=1 (ImagePaintable(ImageBox<IMG>))
|
||||
|
||||
DisplayList:
|
||||
SaveLayer@0
|
||||
CompositorWheelHitTestTarget@0 target_scroll_frame_index=0 rect=[0,0 800x600]
|
||||
CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[0,0 800x40]
|
||||
CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[0,0 800x40]
|
||||
CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[0,0 40x40]
|
||||
Save@1
|
||||
AddClipRect@1 rect=[0,0 40x40]
|
||||
DrawScaledDecodedImageFrame@1 dst_rect=[0,-20 40x80]
|
||||
Restore@1
|
||||
Restore@0
|
||||
|
||||
|
|
@ -9,27 +9,23 @@ SaveLayer@0
|
|||
CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[8,13 784x200]
|
||||
CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[8,13 784x200]
|
||||
CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[8,13 300x200]
|
||||
Save@1
|
||||
AddClipRect@1 rect=[8,13 300x200]
|
||||
PaintNestedDisplayList@1 rect=[8,13 300x200]
|
||||
SaveLayer@0
|
||||
CompositorWheelHitTestTarget@0 target_scroll_frame_index=0 rect=[0,0 300x200]
|
||||
CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[0,0 300x200]
|
||||
FillPath@1 path_bounding_rect=[5,0 55x56]
|
||||
FillPath@1 path_bounding_rect=[1,50 55x55]
|
||||
Save@1
|
||||
AddClipRect@1 rect=[4,10 55x55]
|
||||
SaveLayer@1
|
||||
FillPath@1 path_bounding_rect=[5,0 55x56]
|
||||
FillPath@1 path_bounding_rect=[1,50 55x55]
|
||||
ApplyEffects@1 opacity=1 has_filter=false
|
||||
PaintNestedDisplayList@1 rect=[4,10 55x55]
|
||||
Translate@0 delta=[-4,-10]
|
||||
FillPath@0 path_bounding_rect=[4,10 55x56]
|
||||
Restore@1
|
||||
PaintNestedDisplayList@1 rect=[8,13 300x200]
|
||||
SaveLayer@0
|
||||
CompositorWheelHitTestTarget@0 target_scroll_frame_index=0 rect=[0,0 300x200]
|
||||
CompositorWheelHitTestTarget@1 target_scroll_frame_index=0 rect=[0,0 300x200]
|
||||
FillPath@1 path_bounding_rect=[5,0 55x56]
|
||||
FillPath@1 path_bounding_rect=[1,50 55x55]
|
||||
Save@1
|
||||
AddClipRect@1 rect=[4,10 55x55]
|
||||
SaveLayer@1
|
||||
FillPath@1 path_bounding_rect=[5,0 55x56]
|
||||
FillPath@1 path_bounding_rect=[1,50 55x55]
|
||||
ApplyEffects@1 opacity=1 has_filter=false
|
||||
PaintNestedDisplayList@1 rect=[4,10 55x55]
|
||||
Translate@0 delta=[-4,-10]
|
||||
FillPath@0 path_bounding_rect=[4,10 55x56]
|
||||
Restore@1
|
||||
Restore@1
|
||||
Restore@0
|
||||
Restore@1
|
||||
Restore@1
|
||||
Restore@0
|
||||
Restore@0
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-size: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
asyncTest((done) => {
|
||||
const image = document.createElement("img");
|
||||
image.onload = () => {
|
||||
println(internals.dumpDisplayList());
|
||||
done();
|
||||
};
|
||||
document.body.appendChild(image);
|
||||
image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAAGQAQMAAADRPL3zAAAABlBMVEUwMDCAgIDHGf6uAAAAQ0lEQVRo3u3YMREAQAgDQRzg3+U7gDbl9+y1i4AMNdGrjBBCCCGEXJas84wQQgghhJwWW5kQQgghhPgnEkIIIYSQT1nvD3WWWfRskgAAAABJRU5ErkJggg==";
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in a new issue