LibGfx: Add CanvasCommandList

Moving 2D canvas rasterization into the Compositor process needs a wire
format for canvas mutations that does not depend on LibWeb state. Add
CanvasCommandList as an apply-once log of drawing operations whose
operands are Gfx value types and whose IPC encoders can be shared by
WebContent and the compositor side.
This commit is contained in:
Aliaksandr Kalenik 2026-06-11 23:31:15 +02:00 committed by Alexander Kalenik
parent 956a2b96d5
commit 714cf827db
12 changed files with 747 additions and 0 deletions

View file

@ -9,6 +9,8 @@
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Quad.h>
#include <LibGfx/Rect.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
namespace Gfx {
@ -259,3 +261,31 @@ Matrix<4, float> AffineTransform::to_matrix() const
}
}
namespace IPC {
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::AffineTransform const& transform)
{
TRY(encoder.encode(transform.a()));
TRY(encoder.encode(transform.b()));
TRY(encoder.encode(transform.c()));
TRY(encoder.encode(transform.d()));
TRY(encoder.encode(transform.e()));
TRY(encoder.encode(transform.f()));
return {};
}
template<>
ErrorOr<Gfx::AffineTransform> decode(Decoder& decoder)
{
auto a = TRY(decoder.decode<float>());
auto b = TRY(decoder.decode<float>());
auto c = TRY(decoder.decode<float>());
auto d = TRY(decoder.decode<float>());
auto e = TRY(decoder.decode<float>());
auto f = TRY(decoder.decode<float>());
return Gfx::AffineTransform { a, b, c, d, e, f };
}
}

View file

@ -11,6 +11,7 @@
#include <AK/Forward.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Matrix.h>
#include <LibIPC/Forward.h>
namespace Gfx {
@ -99,3 +100,13 @@ struct AK::Formatter<Gfx::AffineTransform> : Formatter<FormatString> {
return Formatter<FormatString>::format(builder, "[{} {} {} {} {} {}]"sv, value.a(), value.b(), value.c(), value.d(), value.e(), value.f());
}
};
namespace IPC {
template<>
ErrorOr<void> encode(Encoder&, Gfx::AffineTransform const&);
template<>
ErrorOr<Gfx::AffineTransform> decode(Decoder&);
}

View file

@ -4,6 +4,7 @@ set(SOURCES
BitmapExport.cpp
BitmapSequence.cpp
CMYKBitmap.cpp
CanvasCommandList.cpp
Color.cpp
ColorConversion.cpp
ColorSpace.cpp
@ -21,7 +22,9 @@ set(SOURCES
Font/WOFF2/Loader.cpp
FontCascadeList.cpp
GradientPainting.cpp
Gradients.cpp
Matrix4x4.cpp
DecodedImageFrame.cpp
ImageFormats/BMPWriter.cpp
ImageFormats/JPEGWriter.cpp
ImageFormats/PNGWriter.cpp

View file

@ -0,0 +1,355 @@
/*
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <LibGfx/CanvasCommandList.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
namespace Gfx {
CanvasPaintStyle to_canvas_paint_style(PaintStyle const& paint_style)
{
if (auto const* solid_color = as_if<SolidColorPaintStyle>(paint_style))
return solid_color->color();
if (auto const* linear_gradient = as_if<CanvasLinearGradientPaintStyle>(paint_style)) {
return CanvasLinearGradient {
.start_point = linear_gradient->start_point(),
.end_point = linear_gradient->end_point(),
.color_stops = Vector<ColorStop> { linear_gradient->color_stops() },
.repeat_length = linear_gradient->repeat_length(),
};
}
if (auto const* radial_gradient = as_if<CanvasRadialGradientPaintStyle>(paint_style)) {
return CanvasRadialGradient {
.start_center = radial_gradient->start_center(),
.start_radius = radial_gradient->start_radius(),
.end_center = radial_gradient->end_center(),
.end_radius = radial_gradient->end_radius(),
.color_stops = Vector<ColorStop> { radial_gradient->color_stops() },
.repeat_length = radial_gradient->repeat_length(),
};
}
if (auto const* conic_gradient = as_if<CanvasConicGradientPaintStyle>(paint_style)) {
return CanvasConicGradient {
.center = conic_gradient->center(),
.start_angle = conic_gradient->start_angle(),
.color_stops = Vector<ColorStop> { conic_gradient->color_stops() },
.repeat_length = conic_gradient->repeat_length(),
};
}
if (auto const* pattern = as_if<CanvasPatternPaintStyle>(paint_style)) {
return CanvasPatternStyle {
.image = pattern->image(),
.repetition = pattern->repetition(),
.transform = pattern->transform(),
};
}
VERIFY_NOT_REACHED();
}
}
namespace IPC {
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasLinearGradient const& gradient)
{
TRY(encoder.encode(gradient.start_point));
TRY(encoder.encode(gradient.end_point));
TRY(encoder.encode(gradient.color_stops));
TRY(encoder.encode(gradient.repeat_length));
return {};
}
template<>
ErrorOr<Gfx::CanvasLinearGradient> decode(Decoder& decoder)
{
return Gfx::CanvasLinearGradient {
.start_point = TRY(decoder.decode<Gfx::FloatPoint>()),
.end_point = TRY(decoder.decode<Gfx::FloatPoint>()),
.color_stops = TRY(decoder.decode<Vector<Gfx::ColorStop>>()),
.repeat_length = TRY(decoder.decode<Optional<float>>()),
};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasRadialGradient const& gradient)
{
TRY(encoder.encode(gradient.start_center));
TRY(encoder.encode(gradient.start_radius));
TRY(encoder.encode(gradient.end_center));
TRY(encoder.encode(gradient.end_radius));
TRY(encoder.encode(gradient.color_stops));
TRY(encoder.encode(gradient.repeat_length));
return {};
}
template<>
ErrorOr<Gfx::CanvasRadialGradient> decode(Decoder& decoder)
{
return Gfx::CanvasRadialGradient {
.start_center = TRY(decoder.decode<Gfx::FloatPoint>()),
.start_radius = TRY(decoder.decode<float>()),
.end_center = TRY(decoder.decode<Gfx::FloatPoint>()),
.end_radius = TRY(decoder.decode<float>()),
.color_stops = TRY(decoder.decode<Vector<Gfx::ColorStop>>()),
.repeat_length = TRY(decoder.decode<Optional<float>>()),
};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasConicGradient const& gradient)
{
TRY(encoder.encode(gradient.center));
TRY(encoder.encode(gradient.start_angle));
TRY(encoder.encode(gradient.color_stops));
TRY(encoder.encode(gradient.repeat_length));
return {};
}
template<>
ErrorOr<Gfx::CanvasConicGradient> decode(Decoder& decoder)
{
return Gfx::CanvasConicGradient {
.center = TRY(decoder.decode<Gfx::FloatPoint>()),
.start_angle = TRY(decoder.decode<float>()),
.color_stops = TRY(decoder.decode<Vector<Gfx::ColorStop>>()),
.repeat_length = TRY(decoder.decode<Optional<float>>()),
};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasPatternStyle const& pattern)
{
TRY(encoder.encode(pattern.image));
TRY(encoder.encode(pattern.repetition));
TRY(encoder.encode(pattern.transform));
return {};
}
template<>
ErrorOr<Gfx::CanvasPatternStyle> decode(Decoder& decoder)
{
return Gfx::CanvasPatternStyle {
.image = TRY(decoder.decode<Optional<Gfx::DecodedImageFrame>>()),
.repetition = TRY(decoder.decode<Gfx::CanvasPatternPaintStyle::Repetition>()),
.transform = TRY(decoder.decode<Optional<Gfx::AffineTransform>>()),
};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasCommands::ClearRect const& command)
{
TRY(encoder.encode(command.rect));
TRY(encoder.encode(command.color));
return {};
}
template<>
ErrorOr<Gfx::CanvasCommands::ClearRect> decode(Decoder& decoder)
{
return Gfx::CanvasCommands::ClearRect {
.rect = TRY(decoder.decode<Gfx::FloatRect>()),
.color = TRY(decoder.decode<Gfx::Color>()),
};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasCommands::FillRect const& command)
{
TRY(encoder.encode(command.rect));
TRY(encoder.encode(command.color));
return {};
}
template<>
ErrorOr<Gfx::CanvasCommands::FillRect> decode(Decoder& decoder)
{
return Gfx::CanvasCommands::FillRect {
.rect = TRY(decoder.decode<Gfx::FloatRect>()),
.color = TRY(decoder.decode<Gfx::Color>()),
};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasCommands::DrawBitmap const& command)
{
TRY(encoder.encode(command.frame));
TRY(encoder.encode(command.dst_rect));
TRY(encoder.encode(command.src_rect));
TRY(encoder.encode(command.scaling_mode));
TRY(encoder.encode(command.filter));
TRY(encoder.encode(command.global_alpha));
TRY(encoder.encode(command.compositing_and_blending_operator));
return {};
}
template<>
ErrorOr<Gfx::CanvasCommands::DrawBitmap> decode(Decoder& decoder)
{
return Gfx::CanvasCommands::DrawBitmap {
.frame = TRY(decoder.decode<Gfx::DecodedImageFrame>()),
.dst_rect = TRY(decoder.decode<Gfx::FloatRect>()),
.src_rect = TRY(decoder.decode<Gfx::IntRect>()),
.scaling_mode = TRY(decoder.decode<Gfx::ScalingMode>()),
.filter = TRY(decoder.decode<Optional<Gfx::Filter>>()),
.global_alpha = TRY(decoder.decode<float>()),
.compositing_and_blending_operator = TRY(decoder.decode<Gfx::CompositingAndBlendingOperator>()),
};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasCommands::FillPath const& command)
{
TRY(encoder.encode(command.path));
TRY(encoder.encode(command.style));
TRY(encoder.encode(command.winding_rule));
TRY(encoder.encode(command.blur_radius));
TRY(encoder.encode(command.filter));
TRY(encoder.encode(command.global_alpha));
TRY(encoder.encode(command.compositing_and_blending_operator));
return {};
}
template<>
ErrorOr<Gfx::CanvasCommands::FillPath> decode(Decoder& decoder)
{
return Gfx::CanvasCommands::FillPath {
.path = TRY(decoder.decode<Gfx::Path>()),
.style = TRY(decoder.decode<Gfx::CanvasPaintStyle>()),
.winding_rule = TRY(decoder.decode<Gfx::WindingRule>()),
.blur_radius = TRY(decoder.decode<float>()),
.filter = TRY(decoder.decode<Optional<Gfx::Filter>>()),
.global_alpha = TRY(decoder.decode<float>()),
.compositing_and_blending_operator = TRY(decoder.decode<Gfx::CompositingAndBlendingOperator>()),
};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasCommands::StrokePath const& command)
{
TRY(encoder.encode(command.path));
TRY(encoder.encode(command.style));
TRY(encoder.encode(command.thickness));
TRY(encoder.encode(command.cap_style));
TRY(encoder.encode(command.join_style));
TRY(encoder.encode(command.miter_limit));
TRY(encoder.encode(command.dash_array));
TRY(encoder.encode(command.dash_offset));
TRY(encoder.encode(command.blur_radius));
TRY(encoder.encode(command.filter));
TRY(encoder.encode(command.global_alpha));
TRY(encoder.encode(command.compositing_and_blending_operator));
return {};
}
template<>
ErrorOr<Gfx::CanvasCommands::StrokePath> decode(Decoder& decoder)
{
return Gfx::CanvasCommands::StrokePath {
.path = TRY(decoder.decode<Gfx::Path>()),
.style = TRY(decoder.decode<Gfx::CanvasPaintStyle>()),
.thickness = TRY(decoder.decode<float>()),
.cap_style = TRY(decoder.decode<Gfx::Path::CapStyle>()),
.join_style = TRY(decoder.decode<Gfx::Path::JoinStyle>()),
.miter_limit = TRY(decoder.decode<float>()),
.dash_array = TRY(decoder.decode<Vector<float>>()),
.dash_offset = TRY(decoder.decode<float>()),
.blur_radius = TRY(decoder.decode<float>()),
.filter = TRY(decoder.decode<Optional<Gfx::Filter>>()),
.global_alpha = TRY(decoder.decode<float>()),
.compositing_and_blending_operator = TRY(decoder.decode<Gfx::CompositingAndBlendingOperator>()),
};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasCommands::SetTransform const& command)
{
TRY(encoder.encode(command.transform));
return {};
}
template<>
ErrorOr<Gfx::CanvasCommands::SetTransform> decode(Decoder& decoder)
{
return Gfx::CanvasCommands::SetTransform {
.transform = TRY(decoder.decode<Gfx::AffineTransform>()),
};
}
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::Save const&)
{
return {};
}
template<>
ErrorOr<Gfx::CanvasCommands::Save> decode(Decoder&)
{
return Gfx::CanvasCommands::Save {};
}
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::Restore const&)
{
return {};
}
template<>
ErrorOr<Gfx::CanvasCommands::Restore> decode(Decoder&)
{
return Gfx::CanvasCommands::Restore {};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasCommands::ClipPath const& command)
{
TRY(encoder.encode(command.path));
TRY(encoder.encode(command.winding_rule));
return {};
}
template<>
ErrorOr<Gfx::CanvasCommands::ClipPath> decode(Decoder& decoder)
{
return Gfx::CanvasCommands::ClipPath {
.path = TRY(decoder.decode<Gfx::Path>()),
.winding_rule = TRY(decoder.decode<Gfx::WindingRule>()),
};
}
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::Reset const&)
{
return {};
}
template<>
ErrorOr<Gfx::CanvasCommands::Reset> decode(Decoder&)
{
return Gfx::CanvasCommands::Reset {};
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasCommandList const& command_list)
{
TRY(encoder.encode(command_list.commands()));
return {};
}
template<>
ErrorOr<Gfx::CanvasCommandList> decode(Decoder& decoder)
{
return Gfx::CanvasCommandList { TRY(decoder.decode<Vector<Gfx::CanvasCommand>>()) };
}
}

View file

@ -0,0 +1,237 @@
/*
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Color.h>
#include <LibGfx/CompositingAndBlendingOperator.h>
#include <LibGfx/DecodedImageFrame.h>
#include <LibGfx/Filter.h>
#include <LibGfx/Gradients.h>
#include <LibGfx/PaintStyle.h>
#include <LibGfx/Path.h>
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibGfx/ScalingMode.h>
#include <LibGfx/Size.h>
#include <LibGfx/WindingRule.h>
#include <LibIPC/Forward.h>
namespace Gfx {
struct CanvasLinearGradient {
FloatPoint start_point;
FloatPoint end_point;
Vector<ColorStop> color_stops;
Optional<float> repeat_length;
};
struct CanvasRadialGradient {
FloatPoint start_center;
float start_radius { 0 };
FloatPoint end_center;
float end_radius { 0 };
Vector<ColorStop> color_stops;
Optional<float> repeat_length;
};
struct CanvasConicGradient {
FloatPoint center;
float start_angle { 0 };
Vector<ColorStop> color_stops;
Optional<float> repeat_length;
};
struct CanvasPatternStyle {
Optional<DecodedImageFrame> image;
CanvasPatternPaintStyle::Repetition repetition { CanvasPatternPaintStyle::Repetition::Repeat };
Optional<AffineTransform> transform;
};
using CanvasPaintStyle = Variant<Color, CanvasLinearGradient, CanvasRadialGradient, CanvasConicGradient, CanvasPatternStyle>;
namespace CanvasCommands {
struct ClearRect {
FloatRect rect;
Color color;
};
struct FillRect {
FloatRect rect;
Color color;
};
struct DrawBitmap {
DecodedImageFrame frame;
FloatRect dst_rect;
IntRect src_rect;
ScalingMode scaling_mode { ScalingMode::NearestNeighbor };
Optional<Filter> filter;
float global_alpha { 1 };
CompositingAndBlendingOperator compositing_and_blending_operator { CompositingAndBlendingOperator::SourceOver };
};
struct FillPath {
Path path;
CanvasPaintStyle style;
WindingRule winding_rule { WindingRule::Nonzero };
float blur_radius { 0 };
Optional<Filter> filter;
float global_alpha { 1 };
CompositingAndBlendingOperator compositing_and_blending_operator { CompositingAndBlendingOperator::SourceOver };
};
struct StrokePath {
Path path;
CanvasPaintStyle style;
float thickness { 1 };
Path::CapStyle cap_style { Path::CapStyle::Butt };
Path::JoinStyle join_style { Path::JoinStyle::Miter };
float miter_limit { 10 };
Vector<float> dash_array;
float dash_offset { 0 };
float blur_radius { 0 };
Optional<Filter> filter;
float global_alpha { 1 };
CompositingAndBlendingOperator compositing_and_blending_operator { CompositingAndBlendingOperator::SourceOver };
};
struct SetTransform {
AffineTransform transform;
};
struct Save { };
struct Restore { };
struct ClipPath {
Path path;
WindingRule winding_rule { WindingRule::Nonzero };
};
struct Reset { };
}
using CanvasCommand = Variant<
CanvasCommands::ClearRect,
CanvasCommands::FillRect,
CanvasCommands::DrawBitmap,
CanvasCommands::FillPath,
CanvasCommands::StrokePath,
CanvasCommands::SetTransform,
CanvasCommands::Save,
CanvasCommands::Restore,
CanvasCommands::ClipPath,
CanvasCommands::Reset>;
class CanvasCommandList {
public:
CanvasCommandList() = default;
explicit CanvasCommandList(Vector<CanvasCommand> commands)
: m_commands(move(commands))
{
}
void append(CanvasCommand&& command) { m_commands.append(move(command)); }
bool is_empty() const { return m_commands.is_empty(); }
size_t size() const { return m_commands.size(); }
Vector<CanvasCommand> const& commands() const { return m_commands; }
private:
Vector<CanvasCommand> m_commands;
};
CanvasPaintStyle to_canvas_paint_style(PaintStyle const&);
}
namespace IPC {
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasLinearGradient const&);
template<>
ErrorOr<Gfx::CanvasLinearGradient> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasRadialGradient const&);
template<>
ErrorOr<Gfx::CanvasRadialGradient> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasConicGradient const&);
template<>
ErrorOr<Gfx::CanvasConicGradient> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasPatternStyle const&);
template<>
ErrorOr<Gfx::CanvasPatternStyle> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::ClearRect const&);
template<>
ErrorOr<Gfx::CanvasCommands::ClearRect> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::FillRect const&);
template<>
ErrorOr<Gfx::CanvasCommands::FillRect> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::DrawBitmap const&);
template<>
ErrorOr<Gfx::CanvasCommands::DrawBitmap> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::FillPath const&);
template<>
ErrorOr<Gfx::CanvasCommands::FillPath> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::StrokePath const&);
template<>
ErrorOr<Gfx::CanvasCommands::StrokePath> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::SetTransform const&);
template<>
ErrorOr<Gfx::CanvasCommands::SetTransform> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::Save const&);
template<>
ErrorOr<Gfx::CanvasCommands::Save> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::Restore const&);
template<>
ErrorOr<Gfx::CanvasCommands::Restore> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::ClipPath const&);
template<>
ErrorOr<Gfx::CanvasCommands::ClipPath> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::Reset const&);
template<>
ErrorOr<Gfx::CanvasCommands::Reset> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommandList const&);
template<>
ErrorOr<Gfx::CanvasCommandList> decode(Decoder&);
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/DecodedImageFrame.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
namespace IPC {
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::DecodedImageFrame const& frame)
{
auto bitmap = frame.bitmap().to_shareable_bitmap();
TRY(encoder.encode(bitmap));
TRY(encoder.encode(frame.color_space()));
return {};
}
template<>
ErrorOr<Gfx::DecodedImageFrame> decode(Decoder& decoder)
{
auto bitmap = TRY(decoder.decode<Gfx::ShareableBitmap>());
auto color_space = TRY(decoder.decode<Gfx::ColorSpace>());
return Gfx::DecodedImageFrame { *bitmap.bitmap(), move(color_space) };
}
}

View file

@ -13,6 +13,7 @@
#include <LibGfx/ColorSpace.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
#include <LibIPC/Forward.h>
namespace Gfx {
@ -67,3 +68,13 @@ private:
};
}
namespace IPC {
template<>
ErrorOr<void> encode(Encoder&, Gfx::DecodedImageFrame const&);
template<>
ErrorOr<Gfx::DecodedImageFrame> decode(Decoder&);
}

View file

@ -10,6 +10,7 @@ namespace Gfx {
class Bitmap;
class CMYKBitmap;
class CanvasCommandList;
class ColorSpace;
class DecodedImageFrame;
class Color;

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Gradients.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
namespace IPC {
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::ColorStop const& color_stop)
{
TRY(encoder.encode(color_stop.color));
TRY(encoder.encode(color_stop.position));
TRY(encoder.encode(color_stop.transition_hint));
return {};
}
template<>
ErrorOr<Gfx::ColorStop> decode(Decoder& decoder)
{
auto color = TRY(decoder.decode<Gfx::Color>());
auto position = TRY(decoder.decode<float>());
auto transition_hint = TRY(decoder.decode<Optional<float>>());
return Gfx::ColorStop { color, position, transition_hint };
}
}

View file

@ -9,6 +9,7 @@
#include <AK/Math.h>
#include <LibGfx/Color.h>
#include <LibGfx/Size.h>
#include <LibIPC/Forward.h>
namespace Gfx {
@ -45,3 +46,13 @@ inline float calculate_gradient_length(Size<T> gradient_size, float gradient_ang
}
}
namespace IPC {
template<>
ErrorOr<void> encode(Encoder&, Gfx::ColorStop const&);
template<>
ErrorOr<Gfx::ColorStop> decode(Decoder&);
}

View file

@ -43,6 +43,26 @@ ErrorOr<Gfx::IntRect> decode(Decoder& decoder)
return Gfx::IntRect { point, size };
}
template<>
ErrorOr<void> encode(Encoder& encoder, Gfx::FloatRect const& rect)
{
TRY(encoder.encode(rect.x()));
TRY(encoder.encode(rect.y()));
TRY(encoder.encode(rect.width()));
TRY(encoder.encode(rect.height()));
return {};
}
template<>
ErrorOr<Gfx::FloatRect> decode(Decoder& decoder)
{
auto x = TRY(decoder.decode<float>());
auto y = TRY(decoder.decode<float>());
auto width = TRY(decoder.decode<float>());
auto height = TRY(decoder.decode<float>());
return Gfx::FloatRect { x, y, width, height };
}
}
template class Gfx::Rect<int>;

View file

@ -620,4 +620,10 @@ ErrorOr<void> encode(Encoder&, Gfx::IntRect const&);
template<>
ErrorOr<Gfx::IntRect> decode(Decoder&);
template<>
ErrorOr<void> encode(Encoder&, Gfx::FloatRect const&);
template<>
ErrorOr<Gfx::FloatRect> decode(Decoder&);
}