From d7cb7c4c15d789e43c0bb7b9dbf52337505f125e Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 11 Jun 2026 23:55:24 +0200 Subject: [PATCH] LibGfx: Add CanvasCommandPlayer Once canvas commands can be sent to another process, the receiver needs an endpoint that owns the persistent surface and validates command data before it reaches Skia. Add CanvasCommandPlayer for that role. The player replays CanvasCommandList deltas through concrete PainterSkia APIs and keeps painter state across play() calls, matching the way a compositor-hosted canvas surface will accumulate mutations over time. Initialize ops allocate or resize the backing surface so creation, resize and repaint all flow through the same command stream. --- Libraries/LibGfx/CMakeLists.txt | 1 + Libraries/LibGfx/CanvasCommandPlayer.cpp | 131 +++++++++++++++++++++++ Libraries/LibGfx/CanvasCommandPlayer.h | 50 +++++++++ Libraries/LibGfx/Forward.h | 2 + 4 files changed, 184 insertions(+) create mode 100644 Libraries/LibGfx/CanvasCommandPlayer.cpp create mode 100644 Libraries/LibGfx/CanvasCommandPlayer.h diff --git a/Libraries/LibGfx/CMakeLists.txt b/Libraries/LibGfx/CMakeLists.txt index 42d3bacc9d..f1d1c8105a 100644 --- a/Libraries/LibGfx/CMakeLists.txt +++ b/Libraries/LibGfx/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES BitmapSequence.cpp CMYKBitmap.cpp CanvasCommandList.cpp + CanvasCommandPlayer.cpp Color.cpp ColorConversion.cpp ColorSpace.cpp diff --git a/Libraries/LibGfx/CanvasCommandPlayer.cpp b/Libraries/LibGfx/CanvasCommandPlayer.cpp new file mode 100644 index 0000000000..e75da2a8f7 --- /dev/null +++ b/Libraries/LibGfx/CanvasCommandPlayer.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2026, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Gfx { + +CanvasCommandPlayer::CanvasCommandPlayer(RefPtr skia_backend_context, IntSize size, BitmapFormat format, AlphaType alpha_type) + : m_surface(PaintingSurface::create_with_size(size, format, alpha_type, move(skia_backend_context))) + , m_painter(make(*m_surface)) +{ +} + +CanvasCommandPlayer::~CanvasCommandPlayer() = default; + +NonnullRefPtr CanvasCommandPlayer::surface() const +{ + return m_surface; +} + +void CanvasCommandPlayer::clear(Color color) +{ + m_painter->clear_rect({ {}, m_surface->size().to_type() }, color); +} + +void CanvasCommandPlayer::play(CanvasCommandList const& command_list) +{ + for (auto const& command : command_list.commands()) + command.visit([&](auto const& command) { play_command(command); }); +} + +void CanvasCommandPlayer::play_command(CanvasCommands::ClearRect const& command) +{ + m_painter->clear_rect(command.rect, command.color); +} + +void CanvasCommandPlayer::play_command(CanvasCommands::FillRect const& command) +{ + m_painter->fill_rect(command.rect, command.color); +} + +void CanvasCommandPlayer::play_command(CanvasCommands::DrawBitmap const& command) +{ + m_painter->draw_bitmap(command.dst_rect, command.frame, command.src_rect, command.scaling_mode, command.filter, command.global_alpha, command.compositing_and_blending_operator); +} + +void CanvasCommandPlayer::play_command(CanvasCommands::FillPath const& command) +{ + // Shadows are recorded as blurred solid-color fills; everything else goes through the general paint-style overload. + if (command.blur_radius > 0 && command.style.has()) { + m_painter->fill_path(command.path, command.style.get(), command.winding_rule, command.blur_radius, command.compositing_and_blending_operator); + return; + } + + m_painter->fill_path(command.path, resolve_paint_style(command.style), command.filter, command.global_alpha, command.compositing_and_blending_operator, command.winding_rule); +} + +void CanvasCommandPlayer::play_command(CanvasCommands::StrokePath const& command) +{ + if (command.blur_radius > 0 && command.style.has()) { + m_painter->stroke_path(command.path, command.style.get(), command.thickness, command.blur_radius, command.compositing_and_blending_operator, command.cap_style, command.join_style, command.miter_limit, command.dash_array, command.dash_offset); + return; + } + + m_painter->stroke_path(command.path, resolve_paint_style(command.style), command.filter, command.thickness, command.global_alpha, command.compositing_and_blending_operator, command.cap_style, command.join_style, command.miter_limit, command.dash_array, command.dash_offset); +} + +void CanvasCommandPlayer::play_command(CanvasCommands::SetTransform const& command) +{ + m_painter->set_transform(command.transform); +} + +void CanvasCommandPlayer::play_command(CanvasCommands::Save const&) +{ + m_painter->save(); +} + +void CanvasCommandPlayer::play_command(CanvasCommands::Restore const&) +{ + m_painter->restore(); +} + +void CanvasCommandPlayer::play_command(CanvasCommands::ClipPath const& command) +{ + m_painter->clip(command.path, command.winding_rule); +} + +void CanvasCommandPlayer::play_command(CanvasCommands::Reset const&) +{ + m_painter->reset(); +} + +NonnullRefPtr CanvasCommandPlayer::resolve_paint_style(CanvasPaintStyle const& style) const +{ + auto with_color_stops = [](auto paint_style, auto const& gradient) -> NonnullRefPtr { + paint_style->set_color_stops(Vector { gradient.color_stops }); + if (gradient.repeat_length.has_value()) + paint_style->set_repeat_length(*gradient.repeat_length); + return paint_style; + }; + + return style.visit( + [](Color const& color) -> NonnullRefPtr { + return MUST(SolidColorPaintStyle::create(color)); + }, + [&](CanvasLinearGradient const& gradient) -> NonnullRefPtr { + return with_color_stops(MUST(CanvasLinearGradientPaintStyle::create(gradient.start_point, gradient.end_point)), gradient); + }, + [&](CanvasRadialGradient const& gradient) -> NonnullRefPtr { + return with_color_stops(MUST(CanvasRadialGradientPaintStyle::create(gradient.start_center, gradient.start_radius, gradient.end_center, gradient.end_radius)), gradient); + }, + [&](CanvasConicGradient const& gradient) -> NonnullRefPtr { + return with_color_stops(MUST(CanvasConicGradientPaintStyle::create(gradient.center, gradient.start_angle)), gradient); + }, + [&](CanvasPatternStyle const& pattern) -> NonnullRefPtr { + auto paint_style = MUST(CanvasPatternPaintStyle::create(pattern.image, pattern.repetition)); + if (pattern.transform.has_value()) + paint_style->set_transform(*pattern.transform); + return paint_style; + }); +} + +} diff --git a/Libraries/LibGfx/CanvasCommandPlayer.h b/Libraries/LibGfx/CanvasCommandPlayer.h new file mode 100644 index 0000000000..a5d5c2aa24 --- /dev/null +++ b/Libraries/LibGfx/CanvasCommandPlayer.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Gfx { + +class CanvasCommandPlayer { + AK_MAKE_NONCOPYABLE(CanvasCommandPlayer); + AK_MAKE_NONMOVABLE(CanvasCommandPlayer); + +public: + CanvasCommandPlayer(RefPtr, IntSize, BitmapFormat, AlphaType); + ~CanvasCommandPlayer(); + + NonnullRefPtr surface() const; + + void clear(Color); + + void play(CanvasCommandList const&); + +private: + void play_command(CanvasCommands::ClearRect const&); + void play_command(CanvasCommands::FillRect const&); + void play_command(CanvasCommands::DrawBitmap const&); + void play_command(CanvasCommands::FillPath const&); + void play_command(CanvasCommands::StrokePath const&); + void play_command(CanvasCommands::SetTransform const&); + void play_command(CanvasCommands::Save const&); + void play_command(CanvasCommands::Restore const&); + void play_command(CanvasCommands::ClipPath const&); + void play_command(CanvasCommands::Reset const&); + + NonnullRefPtr resolve_paint_style(CanvasPaintStyle const&) const; + + NonnullRefPtr m_surface; + NonnullOwnPtr m_painter; +}; + +} diff --git a/Libraries/LibGfx/Forward.h b/Libraries/LibGfx/Forward.h index 7c3b912724..924d3a7d76 100644 --- a/Libraries/LibGfx/Forward.h +++ b/Libraries/LibGfx/Forward.h @@ -11,6 +11,7 @@ namespace Gfx { class Bitmap; class CMYKBitmap; class CanvasCommandList; +class CanvasCommandPlayer; class ColorSpace; class DecodedImageFrame; class Color; @@ -22,6 +23,7 @@ class ImageDecoder; struct FontPixelMetrics; class Painter; +class PainterSkia; class PaintingSurface; class Palette; class YUVData;