LibWeb: Inline serialized filters in display list commands

Prep work for serializing display lists across the IPC boundary. Replace
Gfx::Filter's Skia-specific backing with a portable Variant-based
representation, add serialize_filter/deserialize_filter, and store
filter data inline in ApplyEffects/ApplyBackdropFilter command payloads
instead of in DisplayListResourceStorage. FilterResourceId is removed
along with the per-storage filter map.
This commit is contained in:
Aliaksandr Kalenik 2026-05-11 14:17:32 +02:00 committed by Alexander Kalenik
parent c1a69e6c9e
commit 3c9df3fccd
11 changed files with 986 additions and 292 deletions

View file

@ -4,44 +4,42 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Atomic.h>
#include <AK/MemoryStream.h>
#include <AK/NumericLimits.h>
#include <LibGfx/DecodedImageFrame.h>
#include <LibGfx/Filter.h>
#include <LibGfx/FilterImpl.h>
#include <LibGfx/SkiaUtils.h>
#include <core/SkBlendMode.h>
#include <core/SkColorFilter.h>
#include <core/SkScalar.h>
#include <effects/SkColorMatrix.h>
#include <effects/SkImageFilters.h>
#include <effects/SkPerlinNoiseShader.h>
namespace Gfx {
static Atomic<u64> s_next_id { 1 };
using Impl = FilterImpl;
static ErrorOr<Optional<ByteBuffer>> copy_optional_color_table(Optional<ReadonlyBytes> bytes)
{
if (!bytes.has_value())
return Optional<ByteBuffer> {};
VERIFY(bytes->size() == 256);
return Optional<ByteBuffer> { TRY(ByteBuffer::copy(*bytes)) };
}
Filter::Filter(Filter const& other)
: m_id(other.m_id)
, m_impl(other.m_impl->clone())
: m_impl(other.m_impl->clone())
{
}
Filter& Filter::operator=(Filter const& other)
{
if (this != &other) {
m_id = other.m_id;
if (this != &other)
m_impl = other.m_impl->clone();
}
return *this;
}
Filter::Filter(Filter&&) = default;
Filter& Filter::operator=(Filter&&) = default;
Filter::~Filter() = default;
Filter::Filter(NonnullOwnPtr<FilterImpl>&& impl)
: m_id(s_next_id.fetch_add(1, AK::MemoryOrder::memory_order_relaxed))
, m_impl(impl->clone())
: m_impl(move(impl))
{
}
@ -52,298 +50,541 @@ FilterImpl const& Filter::impl() const
Filter Filter::arithmetic(Optional<Filter const&> background, Optional<Filter const&> foreground, float k1, float k2, float k3, float k4)
{
sk_sp<SkImageFilter> background_skia = background.has_value() ? background->m_impl->filter : nullptr;
sk_sp<SkImageFilter> foreground_skia = foreground.has_value() ? foreground->m_impl->filter : nullptr;
auto filter = SkImageFilters::Arithmetic(
SkFloatToScalar(k1), SkFloatToScalar(k2), SkFloatToScalar(k3), SkFloatToScalar(k4), false, move(background_skia), move(foreground_skia));
return Filter(Impl::create(filter));
return Filter(FilterImpl::create(FilterImpl::Arithmetic {
.background = background.copy(),
.foreground = foreground.copy(),
.k1 = k1,
.k2 = k2,
.k3 = k3,
.k4 = k4,
}));
}
Filter Filter::compose(Filter const& outer, Filter const& inner)
{
auto inner_skia = inner.m_impl->filter;
auto outer_skia = outer.m_impl->filter;
auto filter = SkImageFilters::Compose(outer_skia, inner_skia);
return Filter(Impl::create(filter));
return Filter(FilterImpl::create(FilterImpl::Compose {
.outer = outer,
.inner = inner,
}));
}
Filter Filter::blend(Optional<Filter const&> background, Optional<Filter const&> foreground, Gfx::CompositingAndBlendingOperator mode)
{
sk_sp<SkImageFilter> background_skia = background.has_value() ? background->m_impl->filter : nullptr;
sk_sp<SkImageFilter> foreground_skia = foreground.has_value() ? foreground->m_impl->filter : nullptr;
auto filter = SkImageFilters::Blend(to_skia_blender(mode), background_skia, foreground_skia);
return Filter(Impl::create(filter));
return Filter(FilterImpl::create(FilterImpl::Blend {
.background = background.copy(),
.foreground = foreground.copy(),
.mode = mode,
}));
}
Filter Filter::blur(float radius_x, float radius_y, Optional<Filter const&> input)
{
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
auto filter = SkImageFilters::Blur(radius_x, radius_y, input_skia);
return Filter(Impl::create(filter));
return Filter(FilterImpl::create(FilterImpl::Blur {
.radius_x = radius_x,
.radius_y = radius_y,
.input = input.copy(),
}));
}
Filter Filter::flood(Gfx::Color color, float opacity)
{
auto color_skia = to_skia_color(color);
color_skia = SkColorSetA(color_skia, static_cast<u8>(opacity * 255));
return Filter(Impl::create(SkImageFilters::Shader(SkShaders::Color(color_skia))));
return Filter(FilterImpl::create(FilterImpl::Flood {
.color = color,
.opacity = opacity,
}));
}
Filter Filter::displacement_map(Optional<Filter const&> color, Optional<Filter const&> displacement, float scale, ChannelSelector x_channel_selector, ChannelSelector y_channel_selector)
{
sk_sp<SkImageFilter> color_skia = color.has_value() ? color->m_impl->filter : nullptr;
sk_sp<SkImageFilter> displacement_skia = displacement.has_value() ? displacement->m_impl->filter : nullptr;
auto convert_channel_selector = [](ChannelSelector channel_selector) {
switch (channel_selector) {
case ChannelSelector::Red:
return SkColorChannel::kR;
case ChannelSelector::Green:
return SkColorChannel::kG;
case ChannelSelector::Blue:
return SkColorChannel::kB;
case ChannelSelector::Alpha:
return SkColorChannel::kA;
}
VERIFY_NOT_REACHED();
};
auto x_channel_selector_skia = convert_channel_selector(x_channel_selector);
auto y_channel_selector_skia = convert_channel_selector(y_channel_selector);
auto filter = SkImageFilters::DisplacementMap(x_channel_selector_skia, y_channel_selector_skia, scale, displacement_skia, color_skia);
return Filter(Impl::create(filter));
return Filter(FilterImpl::create(FilterImpl::DisplacementMap {
.color = color.copy(),
.displacement = displacement.copy(),
.scale = scale,
.x_channel_selector = x_channel_selector,
.y_channel_selector = y_channel_selector,
}));
}
Filter Filter::drop_shadow(float offset_x, float offset_y, float radius, Gfx::Color color,
Optional<Filter const&> input)
Filter Filter::drop_shadow(float offset_x, float offset_y, float radius, Gfx::Color color, Optional<Filter const&> input)
{
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
auto shadow_color = to_skia_color(color);
auto filter = SkImageFilters::DropShadow(offset_x, offset_y, radius, radius, shadow_color, input_skia);
return Filter(Impl::create(filter));
return Filter(FilterImpl::create(FilterImpl::DropShadow {
.offset_x = offset_x,
.offset_y = offset_y,
.radius = radius,
.color = color,
.input = input.copy(),
}));
}
Filter Filter::color(ColorFilterType type, float amount, Optional<Filter const&> input)
{
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
sk_sp<SkColorFilter> color_filter;
// Matrices are taken from https://drafts.fxtf.org/filter-effects-1/#FilterPrimitiveRepresentation
switch (type) {
case ColorFilterType::Grayscale: {
float matrix[20] = {
0.2126f + 0.7874f * (1 - amount), 0.7152f - 0.7152f * (1 - amount),
0.0722f - 0.0722f * (1 - amount), 0, 0,
0.2126f - 0.2126f * (1 - amount), 0.7152f + 0.2848f * (1 - amount),
0.0722f - 0.0722f * (1 - amount), 0, 0,
0.2126f - 0.2126f * (1 - amount), 0.7152f - 0.7152f * (1 - amount),
0.0722f + 0.9278f * (1 - amount), 0, 0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
break;
}
case Gfx::ColorFilterType::Brightness: {
float matrix[20] = {
amount, 0, 0, 0, 0,
0, amount, 0, 0, 0,
0, 0, amount, 0, 0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
break;
}
case Gfx::ColorFilterType::Contrast: {
float intercept = -(0.5f * amount) + 0.5f;
float matrix[20] = {
amount, 0, 0, 0, intercept,
0, amount, 0, 0, intercept,
0, 0, amount, 0, intercept,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
break;
}
case Gfx::ColorFilterType::Invert: {
float matrix[20] = {
1 - 2 * amount, 0, 0, 0, amount,
0, 1 - 2 * amount, 0, 0, amount,
0, 0, 1 - 2 * amount, 0, amount,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
break;
}
case Gfx::ColorFilterType::Opacity: {
float matrix[20] = {
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, amount, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
break;
}
case Gfx::ColorFilterType::Sepia: {
float matrix[20] = {
0.393f + 0.607f * (1 - amount), 0.769f - 0.769f * (1 - amount), 0.189f - 0.189f * (1 - amount), 0,
0,
0.349f - 0.349f * (1 - amount), 0.686f + 0.314f * (1 - amount), 0.168f - 0.168f * (1 - amount), 0,
0,
0.272f - 0.272f * (1 - amount), 0.534f - 0.534f * (1 - amount), 0.131f + 0.869f * (1 - amount), 0,
0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
break;
}
case Gfx::ColorFilterType::Saturate: {
float matrix[20] = {
0.213f + 0.787f * amount, 0.715f - 0.715f * amount, 0.072f - 0.072f * amount, 0, 0,
0.213f - 0.213f * amount, 0.715f + 0.285f * amount, 0.072f - 0.072f * amount, 0, 0,
0.213f - 0.213f * amount, 0.715f - 0.715f * amount, 0.072f + 0.928f * amount, 0, 0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
break;
}
default:
VERIFY_NOT_REACHED();
}
return Filter(Impl::create(SkImageFilters::ColorFilter(color_filter, input_skia)));
return Filter(FilterImpl::create(FilterImpl::ColorFilter {
.type = type,
.amount = amount,
.input = input.copy(),
}));
}
Filter Filter::color_matrix(float matrix[20], Optional<Filter const&> input)
{
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
return Filter(Impl::create(SkImageFilters::ColorFilter(SkColorFilters::Matrix(matrix), input_skia)));
Array<float, 20> matrix_values;
for (size_t i = 0; i < matrix_values.size(); ++i)
matrix_values[i] = matrix[i];
return Filter(FilterImpl::create(FilterImpl::ColorMatrix {
.matrix = matrix_values,
.input = input.copy(),
}));
}
Filter Filter::color_table(Optional<ReadonlyBytes> a, Optional<ReadonlyBytes> r, Optional<ReadonlyBytes> g,
Optional<ReadonlyBytes> b, Optional<Filter const&> input)
Filter Filter::color_table(Optional<ReadonlyBytes> a, Optional<ReadonlyBytes> r, Optional<ReadonlyBytes> g, Optional<ReadonlyBytes> b, Optional<Filter const&> input)
{
VERIFY(!a.has_value() || a->size() == 256);
VERIFY(!r.has_value() || r->size() == 256);
VERIFY(!g.has_value() || g->size() == 256);
VERIFY(!b.has_value() || b->size() == 256);
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
auto* a_table = a.has_value() ? a->data() : nullptr;
auto* r_table = r.has_value() ? r->data() : nullptr;
auto* g_table = g.has_value() ? g->data() : nullptr;
auto* b_table = b.has_value() ? b->data() : nullptr;
// Color tables are applied in linear space by default, so we need to convert twice.
// FIXME: support sRGB space as well (i.e. don't perform these conversions).
auto srgb_to_linear = SkImageFilters::ColorFilter(SkColorFilters::SRGBToLinearGamma(), input_skia);
auto color_table = SkImageFilters::ColorFilter(SkColorFilters::TableARGB(a_table, r_table, g_table, b_table), srgb_to_linear);
auto linear_to_srgb = SkImageFilters::ColorFilter(SkColorFilters::LinearToSRGBGamma(), color_table);
return Filter(Impl::create(linear_to_srgb));
return Filter(FilterImpl::create(FilterImpl::ColorTable {
.a = MUST(copy_optional_color_table(a)),
.r = MUST(copy_optional_color_table(r)),
.g = MUST(copy_optional_color_table(g)),
.b = MUST(copy_optional_color_table(b)),
.input = input.copy(),
}));
}
Filter Filter::saturate(float value, Optional<Filter const&> input)
{
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
SkColorMatrix matrix;
matrix.setSaturation(value);
return Filter(Impl::create(SkImageFilters::ColorFilter(SkColorFilters::Matrix(matrix), input_skia)));
return Filter(FilterImpl::create(FilterImpl::Saturate {
.value = value,
.input = input.copy(),
}));
}
Filter Filter::hue_rotate(float angle_degrees, Optional<Filter const&> input)
{
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
float radians = AK::to_radians(angle_degrees);
auto cosA = cos(radians);
auto sinA = sin(radians);
auto a00 = 0.213f + cosA * 0.787f - sinA * 0.213f;
auto a01 = 0.715f - cosA * 0.715f - sinA * 0.715f;
auto a02 = 0.072f - cosA * 0.072f + sinA * 0.928f;
auto a10 = 0.213f - cosA * 0.213f + sinA * 0.143f;
auto a11 = 0.715f + cosA * 0.285f + sinA * 0.140f;
auto a12 = 0.072f - cosA * 0.072f - sinA * 0.283f;
auto a20 = 0.213f - cosA * 0.213f - sinA * 0.787f;
auto a21 = 0.715f - cosA * 0.715f + sinA * 0.715f;
auto a22 = 0.072f + cosA * 0.928f + sinA * 0.072f;
float matrix[20] = {
a00, a01, a02, 0, 0,
a10, a11, a12, 0, 0,
a20, a21, a22, 0, 0,
0, 0, 0, 1, 0
};
auto color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
return Filter(Impl::create(SkImageFilters::ColorFilter(color_filter, input_skia)));
return Filter(FilterImpl::create(FilterImpl::HueRotate {
.angle_degrees = angle_degrees,
.input = input.copy(),
}));
}
Filter Filter::image(Gfx::DecodedImageFrame const& frame, Gfx::IntRect const& src_rect, Gfx::IntRect const& dest_rect, Gfx::ScalingMode scaling_mode)
{
auto skia_src_rect = to_skia_rect(src_rect);
auto skia_dest_rect = to_skia_rect(dest_rect);
auto sampling_options = to_skia_sampling_options(scaling_mode);
auto image = sk_image_from_bitmap(frame.bitmap(), frame.color_space());
return Filter(Impl::create(SkImageFilters::Image(move(image), skia_src_rect, skia_dest_rect, sampling_options)));
return Filter(FilterImpl::create(FilterImpl::Image {
.frame = frame,
.src_rect = src_rect,
.dest_rect = dest_rect,
.scaling_mode = scaling_mode,
}));
}
Filter Filter::merge(Vector<Optional<Filter>> const& inputs)
{
Vector<sk_sp<SkImageFilter>> skia_filters;
skia_filters.ensure_capacity(inputs.size());
for (auto& filter : inputs)
skia_filters.unchecked_append(filter.has_value() ? filter->m_impl->filter : nullptr);
return Filter(Impl::create(SkImageFilters::Merge(skia_filters.data(), skia_filters.size())));
return Filter(FilterImpl::create(FilterImpl::Merge {
.inputs = inputs,
}));
}
Filter Filter::erode(float radius_x, float radius_y, Optional<Filter> const& input)
{
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
return Filter(Impl::create(SkImageFilters::Erode(radius_x, radius_y, input_skia)));
return Filter(FilterImpl::create(FilterImpl::Erode {
.radius_x = radius_x,
.radius_y = radius_y,
.input = input,
}));
}
Filter Filter::dilate(float radius_x, float radius_y, Optional<Filter> const& input)
{
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
return Filter(Impl::create(SkImageFilters::Dilate(radius_x, radius_y, input_skia)));
return Filter(FilterImpl::create(FilterImpl::Dilate {
.radius_x = radius_x,
.radius_y = radius_y,
.input = input,
}));
}
Filter Filter::offset(float dx, float dy, Optional<Filter const&> input)
{
sk_sp<SkImageFilter> input_skia = input.has_value() ? input->m_impl->filter : nullptr;
return Filter(Impl::create(SkImageFilters::Offset(dx, dy, input_skia)));
return Filter(FilterImpl::create(FilterImpl::Offset {
.dx = dx,
.dy = dy,
.input = input.copy(),
}));
}
Filter Filter::turbulence(TurbulenceType turbulence_type, float base_frequency_x, float base_frequency_y, i32 num_octaves, float seed, Gfx::IntSize const& tile_stitch_size)
{
sk_sp<SkShader> turbulence_shader = [&] {
auto skia_size = SkISize::Make(tile_stitch_size.width(), tile_stitch_size.height());
switch (turbulence_type) {
case TurbulenceType::Turbulence:
return SkShaders::MakeTurbulence(base_frequency_x, base_frequency_y, num_octaves, seed, &skia_size);
case TurbulenceType::FractalNoise:
return SkShaders::MakeFractalNoise(base_frequency_x, base_frequency_y, num_octaves, seed, &skia_size);
}
VERIFY_NOT_REACHED();
}();
return Filter(FilterImpl::create(FilterImpl::Turbulence {
.turbulence_type = turbulence_type,
.base_frequency_x = base_frequency_x,
.base_frequency_y = base_frequency_y,
.num_octaves = num_octaves,
.seed = seed,
.tile_stitch_size = tile_stitch_size,
}));
}
return Filter(Impl::create(SkImageFilters::Shader(move(turbulence_shader))));
namespace {
using ImageEncoder = Function<u64(Gfx::DecodedImageFrame const&)>;
using ImageDecoder = Function<Gfx::DecodedImageFrame(u64)>;
static void write_color(Stream& stream, Color color)
{
MUST(stream.write_value<u32>(color.value()));
}
static Color read_color(Stream& stream)
{
return Color::from_bgra(MUST(stream.read_value<u32>()));
}
static void write_int_rect(Stream& stream, Gfx::IntRect const& rect)
{
MUST(stream.write_value<i32>(rect.x()));
MUST(stream.write_value<i32>(rect.y()));
MUST(stream.write_value<i32>(rect.width()));
MUST(stream.write_value<i32>(rect.height()));
}
static Gfx::IntRect read_int_rect(Stream& stream)
{
auto x = MUST(stream.read_value<i32>());
auto y = MUST(stream.read_value<i32>());
auto width = MUST(stream.read_value<i32>());
auto height = MUST(stream.read_value<i32>());
return Gfx::IntRect { x, y, width, height };
}
static void write_int_size(Stream& stream, Gfx::IntSize const& size)
{
MUST(stream.write_value<i32>(size.width()));
MUST(stream.write_value<i32>(size.height()));
}
static Gfx::IntSize read_int_size(Stream& stream)
{
auto width = MUST(stream.read_value<i32>());
auto height = MUST(stream.read_value<i32>());
return Gfx::IntSize { width, height };
}
static void write_bytes(Stream& stream, ReadonlyBytes bytes)
{
VERIFY(bytes.size() <= NumericLimits<u32>::max());
MUST(stream.write_value<u32>(bytes.size()));
MUST(stream.write_until_depleted(bytes));
}
static ByteBuffer read_bytes(Stream& stream)
{
auto size = MUST(stream.read_value<u32>());
auto buffer = MUST(ByteBuffer::create_uninitialized(size));
MUST(stream.read_until_filled(buffer));
return buffer;
}
static void encode_filter(Stream&, Filter const&, ImageEncoder const&);
template<typename T>
static void encode_optional_filter(Stream& stream, Optional<T> const& filter, ImageEncoder const& encode_image)
{
MUST(stream.write_value<bool>(filter.has_value()));
if (filter.has_value())
encode_filter(stream, *filter, encode_image);
}
static void encode_filter(Stream& stream, Filter const& filter, ImageEncoder const& encode_image)
{
filter.impl().operation.visit(
[&](FilterImpl::Arithmetic const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Arithmetic));
encode_optional_filter(stream, op.background, encode_image);
encode_optional_filter(stream, op.foreground, encode_image);
MUST(stream.write_value(op.k1));
MUST(stream.write_value(op.k2));
MUST(stream.write_value(op.k3));
MUST(stream.write_value(op.k4));
},
[&](FilterImpl::Compose const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Compose));
encode_filter(stream, op.outer, encode_image);
encode_filter(stream, op.inner, encode_image);
},
[&](FilterImpl::Blend const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Blend));
encode_optional_filter(stream, op.background, encode_image);
encode_optional_filter(stream, op.foreground, encode_image);
MUST(stream.write_value(op.mode));
},
[&](FilterImpl::Flood const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Flood));
write_color(stream, op.color);
MUST(stream.write_value(op.opacity));
},
[&](FilterImpl::DisplacementMap const& op) {
MUST(stream.write_value(FilterImpl::OperationType::DisplacementMap));
encode_optional_filter(stream, op.color, encode_image);
encode_optional_filter(stream, op.displacement, encode_image);
MUST(stream.write_value(op.scale));
MUST(stream.write_value(op.x_channel_selector));
MUST(stream.write_value(op.y_channel_selector));
},
[&](FilterImpl::DropShadow const& op) {
MUST(stream.write_value(FilterImpl::OperationType::DropShadow));
MUST(stream.write_value(op.offset_x));
MUST(stream.write_value(op.offset_y));
MUST(stream.write_value(op.radius));
write_color(stream, op.color);
encode_optional_filter(stream, op.input, encode_image);
},
[&](FilterImpl::Blur const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Blur));
MUST(stream.write_value(op.radius_x));
MUST(stream.write_value(op.radius_y));
encode_optional_filter(stream, op.input, encode_image);
},
[&](FilterImpl::ColorFilter const& op) {
MUST(stream.write_value(FilterImpl::OperationType::ColorFilter));
MUST(stream.write_value(op.type));
MUST(stream.write_value(op.amount));
encode_optional_filter(stream, op.input, encode_image);
},
[&](FilterImpl::ColorMatrix const& op) {
MUST(stream.write_value(FilterImpl::OperationType::ColorMatrix));
for (auto value : op.matrix)
MUST(stream.write_value(value));
encode_optional_filter(stream, op.input, encode_image);
},
[&](FilterImpl::ColorTable const& op) {
MUST(stream.write_value(FilterImpl::OperationType::ColorTable));
auto encode_optional_color_table = [&](Optional<ByteBuffer> const& bytes) {
MUST(stream.write_value<bool>(bytes.has_value()));
if (bytes.has_value())
write_bytes(stream, *bytes);
};
encode_optional_color_table(op.a);
encode_optional_color_table(op.r);
encode_optional_color_table(op.g);
encode_optional_color_table(op.b);
encode_optional_filter(stream, op.input, encode_image);
},
[&](FilterImpl::Saturate const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Saturate));
MUST(stream.write_value(op.value));
encode_optional_filter(stream, op.input, encode_image);
},
[&](FilterImpl::HueRotate const& op) {
MUST(stream.write_value(FilterImpl::OperationType::HueRotate));
MUST(stream.write_value(op.angle_degrees));
encode_optional_filter(stream, op.input, encode_image);
},
[&](FilterImpl::Image const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Image));
MUST(stream.write_value<u64>(encode_image(op.frame)));
write_int_rect(stream, op.src_rect);
write_int_rect(stream, op.dest_rect);
MUST(stream.write_value(op.scaling_mode));
},
[&](FilterImpl::Merge const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Merge));
VERIFY(op.inputs.size() <= NumericLimits<u32>::max());
MUST(stream.write_value<u32>(op.inputs.size()));
for (auto const& input : op.inputs)
encode_optional_filter(stream, input, encode_image);
},
[&](FilterImpl::Offset const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Offset));
MUST(stream.write_value(op.dx));
MUST(stream.write_value(op.dy));
encode_optional_filter(stream, op.input, encode_image);
},
[&](FilterImpl::Erode const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Erode));
MUST(stream.write_value(op.radius_x));
MUST(stream.write_value(op.radius_y));
encode_optional_filter(stream, op.input, encode_image);
},
[&](FilterImpl::Dilate const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Dilate));
MUST(stream.write_value(op.radius_x));
MUST(stream.write_value(op.radius_y));
encode_optional_filter(stream, op.input, encode_image);
},
[&](FilterImpl::Turbulence const& op) {
MUST(stream.write_value(FilterImpl::OperationType::Turbulence));
MUST(stream.write_value(op.turbulence_type));
MUST(stream.write_value(op.base_frequency_x));
MUST(stream.write_value(op.base_frequency_y));
MUST(stream.write_value(op.num_octaves));
MUST(stream.write_value(op.seed));
write_int_size(stream, op.tile_stitch_size);
});
}
static Optional<Filter> decode_optional_filter(Stream&, ImageDecoder const&);
static Filter decode_filter(Stream& stream, ImageDecoder const& decode_image)
{
auto operation_type = MUST(stream.read_value<FilterImpl::OperationType>());
switch (operation_type) {
case FilterImpl::OperationType::Arithmetic: {
auto background = decode_optional_filter(stream, decode_image);
auto foreground = decode_optional_filter(stream, decode_image);
auto k1 = MUST(stream.read_value<float>());
auto k2 = MUST(stream.read_value<float>());
auto k3 = MUST(stream.read_value<float>());
auto k4 = MUST(stream.read_value<float>());
return Filter::arithmetic(background, foreground, k1, k2, k3, k4);
}
case FilterImpl::OperationType::Compose: {
auto outer = decode_filter(stream, decode_image);
auto inner = decode_filter(stream, decode_image);
return Filter::compose(outer, inner);
}
case FilterImpl::OperationType::Blend: {
auto background = decode_optional_filter(stream, decode_image);
auto foreground = decode_optional_filter(stream, decode_image);
auto mode = MUST(stream.read_value<Gfx::CompositingAndBlendingOperator>());
return Filter::blend(background, foreground, mode);
}
case FilterImpl::OperationType::Flood: {
auto color = read_color(stream);
auto opacity = MUST(stream.read_value<float>());
return Filter::flood(color, opacity);
}
case FilterImpl::OperationType::DisplacementMap: {
auto color = decode_optional_filter(stream, decode_image);
auto displacement = decode_optional_filter(stream, decode_image);
auto scale = MUST(stream.read_value<float>());
auto x_channel_selector = MUST(stream.read_value<ChannelSelector>());
auto y_channel_selector = MUST(stream.read_value<ChannelSelector>());
return Filter::displacement_map(color, displacement, scale, x_channel_selector, y_channel_selector);
}
case FilterImpl::OperationType::DropShadow: {
auto offset_x = MUST(stream.read_value<float>());
auto offset_y = MUST(stream.read_value<float>());
auto radius = MUST(stream.read_value<float>());
auto color = read_color(stream);
auto input = decode_optional_filter(stream, decode_image);
return Filter::drop_shadow(offset_x, offset_y, radius, color, input);
}
case FilterImpl::OperationType::Blur: {
auto radius_x = MUST(stream.read_value<float>());
auto radius_y = MUST(stream.read_value<float>());
auto input = decode_optional_filter(stream, decode_image);
return Filter::blur(radius_x, radius_y, input);
}
case FilterImpl::OperationType::ColorFilter: {
auto type = MUST(stream.read_value<ColorFilterType>());
auto amount = MUST(stream.read_value<float>());
auto input = decode_optional_filter(stream, decode_image);
return Filter::color(type, amount, input);
}
case FilterImpl::OperationType::ColorMatrix: {
Array<float, 20> matrix_values;
for (auto& value : matrix_values)
value = MUST(stream.read_value<float>());
auto input = decode_optional_filter(stream, decode_image);
return Filter::color_matrix(matrix_values.data(), input);
}
case FilterImpl::OperationType::ColorTable: {
auto decode_optional_color_table = [&]() -> Optional<ByteBuffer> {
auto has_value = MUST(stream.read_value<bool>());
if (!has_value)
return {};
auto bytes = read_bytes(stream);
VERIFY(bytes.size() == 256);
return Optional<ByteBuffer> { move(bytes) };
};
auto a = decode_optional_color_table();
auto r = decode_optional_color_table();
auto g = decode_optional_color_table();
auto b = decode_optional_color_table();
auto input = decode_optional_filter(stream, decode_image);
return Filter::color_table(a.has_value() ? Optional<ReadonlyBytes>(a->bytes()) : Optional<ReadonlyBytes> {},
r.has_value() ? Optional<ReadonlyBytes>(r->bytes()) : Optional<ReadonlyBytes> {},
g.has_value() ? Optional<ReadonlyBytes>(g->bytes()) : Optional<ReadonlyBytes> {},
b.has_value() ? Optional<ReadonlyBytes>(b->bytes()) : Optional<ReadonlyBytes> {},
input);
}
case FilterImpl::OperationType::Saturate: {
auto value = MUST(stream.read_value<float>());
auto input = decode_optional_filter(stream, decode_image);
return Filter::saturate(value, input);
}
case FilterImpl::OperationType::HueRotate: {
auto angle_degrees = MUST(stream.read_value<float>());
auto input = decode_optional_filter(stream, decode_image);
return Filter::hue_rotate(angle_degrees, input);
}
case FilterImpl::OperationType::Image: {
auto image_id = MUST(stream.read_value<u64>());
auto frame = decode_image(image_id);
auto src_rect = read_int_rect(stream);
auto dest_rect = read_int_rect(stream);
auto scaling_mode = MUST(stream.read_value<Gfx::ScalingMode>());
return Filter::image(frame, src_rect, dest_rect, scaling_mode);
}
case FilterImpl::OperationType::Merge: {
Vector<Optional<Filter>> inputs;
auto size = MUST(stream.read_value<u32>());
inputs.ensure_capacity(size);
for (size_t i = 0; i < size; ++i)
inputs.unchecked_append(decode_optional_filter(stream, decode_image));
return Filter::merge(inputs);
}
case FilterImpl::OperationType::Offset: {
auto dx = MUST(stream.read_value<float>());
auto dy = MUST(stream.read_value<float>());
auto input = decode_optional_filter(stream, decode_image);
return Filter::offset(dx, dy, input);
}
case FilterImpl::OperationType::Erode: {
auto radius_x = MUST(stream.read_value<float>());
auto radius_y = MUST(stream.read_value<float>());
auto input = decode_optional_filter(stream, decode_image);
return Filter::erode(radius_x, radius_y, input);
}
case FilterImpl::OperationType::Dilate: {
auto radius_x = MUST(stream.read_value<float>());
auto radius_y = MUST(stream.read_value<float>());
auto input = decode_optional_filter(stream, decode_image);
return Filter::dilate(radius_x, radius_y, input);
}
case FilterImpl::OperationType::Turbulence: {
auto turbulence_type = MUST(stream.read_value<TurbulenceType>());
auto base_frequency_x = MUST(stream.read_value<float>());
auto base_frequency_y = MUST(stream.read_value<float>());
auto num_octaves = MUST(stream.read_value<i32>());
auto seed = MUST(stream.read_value<float>());
auto tile_stitch_size = read_int_size(stream);
return Filter::turbulence(turbulence_type, base_frequency_x, base_frequency_y, num_octaves, seed, tile_stitch_size);
}
}
VERIFY_NOT_REACHED();
}
static Optional<Filter> decode_optional_filter(Stream& stream, ImageDecoder const& decode_image)
{
auto has_value = MUST(stream.read_value<bool>());
if (!has_value)
return {};
return decode_filter(stream, decode_image);
}
}
ByteBuffer serialize_filter(Filter const& filter, Function<u64(Gfx::DecodedImageFrame const&)> const& encode_image)
{
AllocatingMemoryStream stream;
encode_filter(stream, filter, encode_image);
auto buffer = MUST(ByteBuffer::create_uninitialized(stream.used_buffer_size()));
MUST(stream.read_until_filled(buffer));
return buffer;
}
Filter deserialize_filter(ReadonlyBytes bytes, Function<Gfx::DecodedImageFrame(u64)> const& decode_image)
{
FixedMemoryStream stream { bytes };
auto filter = decode_filter(stream, decode_image);
VERIFY(stream.is_eof());
return filter;
}
}

View file

@ -6,10 +6,14 @@
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Error.h>
#include <AK/Function.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/Types.h>
#include <LibGfx/Color.h>
#include <LibGfx/CompositingAndBlendingOperator.h>
#include <LibGfx/DecodedImageFrame.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
#include <LibGfx/ScalingMode.h>
@ -44,6 +48,8 @@ class Filter {
public:
Filter(Filter const&);
Filter& operator=(Filter const&);
Filter(Filter&&);
Filter& operator=(Filter&&);
~Filter();
@ -67,12 +73,13 @@ public:
static Filter turbulence(TurbulenceType turbulence_type, float base_frequency_x, float base_frequency_y, i32 num_octaves, float seed, Gfx::IntSize const& tile_stitch_size);
FilterImpl const& impl() const;
u64 id() const { return m_id; }
private:
Filter(NonnullOwnPtr<FilterImpl>&&);
u64 m_id { 0 };
NonnullOwnPtr<FilterImpl> m_impl;
};
ByteBuffer serialize_filter(Filter const&, Function<u64(Gfx::DecodedImageFrame const&)> const& encode_image);
Filter deserialize_filter(ReadonlyBytes, Function<Gfx::DecodedImageFrame(u64)> const& decode_image);
}

View file

@ -6,23 +6,182 @@
#pragma once
#include <AK/Array.h>
#include <AK/ByteBuffer.h>
#include <AK/NonnullOwnPtr.h>
#include <core/SkColorFilter.h>
#include <effects/SkImageFilters.h>
#include <AK/Optional.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibGfx/Filter.h>
namespace Gfx {
struct FilterImpl {
sk_sp<SkImageFilter> filter;
struct Arithmetic {
Optional<Filter> background;
Optional<Filter> foreground;
float k1 { 0.0f };
float k2 { 0.0f };
float k3 { 0.0f };
float k4 { 0.0f };
};
static NonnullOwnPtr<FilterImpl> create(sk_sp<SkImageFilter> filter)
struct Compose {
Filter outer;
Filter inner;
};
struct Blend {
Optional<Filter> background;
Optional<Filter> foreground;
Gfx::CompositingAndBlendingOperator mode { Gfx::CompositingAndBlendingOperator::Normal };
};
struct Flood {
Gfx::Color color;
float opacity { 1.0f };
};
struct DisplacementMap {
Optional<Filter> color;
Optional<Filter> displacement;
float scale { 0.0f };
ChannelSelector x_channel_selector { ChannelSelector::Alpha };
ChannelSelector y_channel_selector { ChannelSelector::Alpha };
};
struct DropShadow {
float offset_x { 0.0f };
float offset_y { 0.0f };
float radius { 0.0f };
Gfx::Color color;
Optional<Filter> input;
};
struct Blur {
float radius_x { 0.0f };
float radius_y { 0.0f };
Optional<Filter> input;
};
struct ColorFilter {
ColorFilterType type { ColorFilterType::Brightness };
float amount { 0.0f };
Optional<Filter> input;
};
struct ColorMatrix {
Array<float, 20> matrix;
Optional<Filter> input;
};
struct ColorTable {
Optional<ByteBuffer> a;
Optional<ByteBuffer> r;
Optional<ByteBuffer> g;
Optional<ByteBuffer> b;
Optional<Filter> input;
};
struct Saturate {
float value { 0.0f };
Optional<Filter> input;
};
struct HueRotate {
float angle_degrees { 0.0f };
Optional<Filter> input;
};
struct Image {
Gfx::DecodedImageFrame frame;
Gfx::IntRect src_rect;
Gfx::IntRect dest_rect;
Gfx::ScalingMode scaling_mode { Gfx::ScalingMode::NearestNeighbor };
};
struct Merge {
Vector<Optional<Filter>> inputs;
};
struct Offset {
float dx { 0.0f };
float dy { 0.0f };
Optional<Filter> input;
};
struct Erode {
float radius_x { 0.0f };
float radius_y { 0.0f };
Optional<Filter> input;
};
struct Dilate {
float radius_x { 0.0f };
float radius_y { 0.0f };
Optional<Filter> input;
};
struct Turbulence {
TurbulenceType turbulence_type { TurbulenceType::Turbulence };
float base_frequency_x { 0.0f };
float base_frequency_y { 0.0f };
i32 num_octaves { 0 };
float seed { 0.0f };
Gfx::IntSize tile_stitch_size;
};
using Operation = Variant<
Arithmetic,
Compose,
Blend,
Flood,
DisplacementMap,
DropShadow,
Blur,
ColorFilter,
ColorMatrix,
ColorTable,
Saturate,
HueRotate,
Image,
Merge,
Offset,
Erode,
Dilate,
Turbulence>;
enum class OperationType : u8 {
Arithmetic,
Compose,
Blend,
Flood,
DisplacementMap,
DropShadow,
Blur,
ColorFilter,
ColorMatrix,
ColorTable,
Saturate,
HueRotate,
Image,
Merge,
Offset,
Erode,
Dilate,
Turbulence,
};
Operation operation;
static NonnullOwnPtr<FilterImpl> create(Operation operation)
{
return adopt_own(*new FilterImpl(move(filter)));
return adopt_own(*new FilterImpl(move(operation)));
}
NonnullOwnPtr<FilterImpl> clone() const
{
return adopt_own(*new FilterImpl(filter));
return adopt_own(*new FilterImpl(operation));
}
};

View file

@ -5,17 +5,23 @@
*/
#include <AK/Assertions.h>
#include <AK/Math.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ColorSpace.h>
#include <LibGfx/DecodedImageFrame.h>
#include <LibGfx/Filter.h>
#include <LibGfx/FilterImpl.h>
#include <LibGfx/SkiaUtils.h>
#include <core/SkBitmap.h>
#include <core/SkBlender.h>
#include <core/SkColorFilter.h>
#include <core/SkColorSpace.h>
#include <core/SkImage.h>
#include <core/SkImageFilter.h>
#include <core/SkString.h>
#include <effects/SkColorMatrix.h>
#include <effects/SkImageFilters.h>
#include <effects/SkPerlinNoiseShader.h>
#include <effects/SkRuntimeEffect.h>
namespace Gfx {
@ -27,7 +33,253 @@ SkPath to_skia_path(Path const& path)
sk_sp<SkImageFilter> to_skia_image_filter(Gfx::Filter const& filter)
{
return filter.impl().filter;
auto to_optional_skia_image_filter = [](Optional<Gfx::Filter> const& input) -> sk_sp<SkImageFilter> {
if (!input.has_value())
return nullptr;
return to_skia_image_filter(input.value());
};
return filter.impl().operation.visit(
[&](FilterImpl::Arithmetic const& op) -> sk_sp<SkImageFilter> {
auto background = to_optional_skia_image_filter(op.background);
auto foreground = to_optional_skia_image_filter(op.foreground);
return SkImageFilters::Arithmetic(
SkFloatToScalar(op.k1),
SkFloatToScalar(op.k2),
SkFloatToScalar(op.k3),
SkFloatToScalar(op.k4),
false,
move(background),
move(foreground));
},
[&](FilterImpl::Compose const& op) -> sk_sp<SkImageFilter> {
auto outer = to_skia_image_filter(op.outer);
auto inner = to_skia_image_filter(op.inner);
return SkImageFilters::Compose(outer, inner);
},
[&](FilterImpl::Blend const& op) -> sk_sp<SkImageFilter> {
auto background = to_optional_skia_image_filter(op.background);
auto foreground = to_optional_skia_image_filter(op.foreground);
return SkImageFilters::Blend(to_skia_blender(op.mode), background, foreground);
},
[&](FilterImpl::Flood const& op) -> sk_sp<SkImageFilter> {
auto color = to_skia_color(op.color);
color = SkColorSetA(color, static_cast<u8>(op.opacity * 255));
return SkImageFilters::Shader(SkShaders::Color(color));
},
[&](FilterImpl::DisplacementMap const& op) -> sk_sp<SkImageFilter> {
auto color = to_optional_skia_image_filter(op.color);
auto displacement = to_optional_skia_image_filter(op.displacement);
auto convert_channel_selector = [](ChannelSelector channel_selector) {
switch (channel_selector) {
case ChannelSelector::Red:
return SkColorChannel::kR;
case ChannelSelector::Green:
return SkColorChannel::kG;
case ChannelSelector::Blue:
return SkColorChannel::kB;
case ChannelSelector::Alpha:
return SkColorChannel::kA;
}
VERIFY_NOT_REACHED();
};
return SkImageFilters::DisplacementMap(
convert_channel_selector(op.x_channel_selector),
convert_channel_selector(op.y_channel_selector),
op.scale,
displacement,
color);
},
[&](FilterImpl::DropShadow const& op) -> sk_sp<SkImageFilter> {
auto input = to_optional_skia_image_filter(op.input);
return SkImageFilters::DropShadow(op.offset_x, op.offset_y, op.radius, op.radius, to_skia_color(op.color), input);
},
[&](FilterImpl::Blur const& op) -> sk_sp<SkImageFilter> {
auto input = to_optional_skia_image_filter(op.input);
return SkImageFilters::Blur(op.radius_x, op.radius_y, input);
},
[&](FilterImpl::ColorFilter const& op) -> sk_sp<SkImageFilter> {
auto input = to_optional_skia_image_filter(op.input);
sk_sp<SkColorFilter> color_filter;
// Matrices are taken from https://drafts.fxtf.org/filter-effects-1/#FilterPrimitiveRepresentation
switch (op.type) {
case ColorFilterType::Grayscale: {
auto amount = op.amount;
float matrix[20] = {
0.2126f + 0.7874f * (1 - amount), 0.7152f - 0.7152f * (1 - amount),
0.0722f - 0.0722f * (1 - amount), 0, 0,
0.2126f - 0.2126f * (1 - amount), 0.7152f + 0.2848f * (1 - amount),
0.0722f - 0.0722f * (1 - amount), 0, 0,
0.2126f - 0.2126f * (1 - amount), 0.7152f - 0.7152f * (1 - amount),
0.0722f + 0.9278f * (1 - amount), 0, 0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
break;
}
case Gfx::ColorFilterType::Brightness: {
auto amount = op.amount;
float matrix[20] = {
amount, 0, 0, 0, 0,
0, amount, 0, 0, 0,
0, 0, amount, 0, 0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
break;
}
case Gfx::ColorFilterType::Contrast: {
auto amount = op.amount;
float intercept = -(0.5f * amount) + 0.5f;
float matrix[20] = {
amount, 0, 0, 0, intercept,
0, amount, 0, 0, intercept,
0, 0, amount, 0, intercept,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
break;
}
case Gfx::ColorFilterType::Invert: {
auto amount = op.amount;
float matrix[20] = {
1 - 2 * amount, 0, 0, 0, amount,
0, 1 - 2 * amount, 0, 0, amount,
0, 0, 1 - 2 * amount, 0, amount,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
break;
}
case Gfx::ColorFilterType::Opacity: {
auto amount = op.amount;
float matrix[20] = {
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, amount, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
break;
}
case Gfx::ColorFilterType::Sepia: {
auto amount = op.amount;
float matrix[20] = {
0.393f + 0.607f * (1 - amount), 0.769f - 0.769f * (1 - amount), 0.189f - 0.189f * (1 - amount), 0,
0,
0.349f - 0.349f * (1 - amount), 0.686f + 0.314f * (1 - amount), 0.168f - 0.168f * (1 - amount), 0,
0,
0.272f - 0.272f * (1 - amount), 0.534f - 0.534f * (1 - amount), 0.131f + 0.869f * (1 - amount), 0,
0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kYes);
break;
}
case Gfx::ColorFilterType::Saturate: {
auto amount = op.amount;
float matrix[20] = {
0.213f + 0.787f * amount, 0.715f - 0.715f * amount, 0.072f - 0.072f * amount, 0, 0,
0.213f - 0.213f * amount, 0.715f + 0.285f * amount, 0.072f - 0.072f * amount, 0, 0,
0.213f - 0.213f * amount, 0.715f - 0.715f * amount, 0.072f + 0.928f * amount, 0, 0,
0, 0, 0, 1, 0
};
color_filter = SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo);
break;
}
default:
VERIFY_NOT_REACHED();
}
return SkImageFilters::ColorFilter(color_filter, input);
},
[&](FilterImpl::ColorMatrix const& op) -> sk_sp<SkImageFilter> {
auto input = to_optional_skia_image_filter(op.input);
return SkImageFilters::ColorFilter(SkColorFilters::Matrix(op.matrix.data()), input);
},
[&](FilterImpl::ColorTable const& op) -> sk_sp<SkImageFilter> {
auto input = to_optional_skia_image_filter(op.input);
auto* a_table = op.a.has_value() ? op.a->data() : nullptr;
auto* r_table = op.r.has_value() ? op.r->data() : nullptr;
auto* g_table = op.g.has_value() ? op.g->data() : nullptr;
auto* b_table = op.b.has_value() ? op.b->data() : nullptr;
// Color tables are applied in linear space by default, so we need to convert twice.
// FIXME: support sRGB space as well (i.e. don't perform these conversions).
auto srgb_to_linear = SkImageFilters::ColorFilter(SkColorFilters::SRGBToLinearGamma(), input);
auto color_table = SkImageFilters::ColorFilter(SkColorFilters::TableARGB(a_table, r_table, g_table, b_table), srgb_to_linear);
return SkImageFilters::ColorFilter(SkColorFilters::LinearToSRGBGamma(), color_table);
},
[&](FilterImpl::Saturate const& op) -> sk_sp<SkImageFilter> {
auto input = to_optional_skia_image_filter(op.input);
SkColorMatrix matrix;
matrix.setSaturation(op.value);
return SkImageFilters::ColorFilter(SkColorFilters::Matrix(matrix), input);
},
[&](FilterImpl::HueRotate const& op) -> sk_sp<SkImageFilter> {
auto input = to_optional_skia_image_filter(op.input);
float radians = AK::to_radians(op.angle_degrees);
auto cosA = cos(radians);
auto sinA = sin(radians);
auto a00 = 0.213f + cosA * 0.787f - sinA * 0.213f;
auto a01 = 0.715f - cosA * 0.715f - sinA * 0.715f;
auto a02 = 0.072f - cosA * 0.072f + sinA * 0.928f;
auto a10 = 0.213f - cosA * 0.213f + sinA * 0.143f;
auto a11 = 0.715f + cosA * 0.285f + sinA * 0.140f;
auto a12 = 0.072f - cosA * 0.072f - sinA * 0.283f;
auto a20 = 0.213f - cosA * 0.213f - sinA * 0.787f;
auto a21 = 0.715f - cosA * 0.715f + sinA * 0.715f;
auto a22 = 0.072f + cosA * 0.928f + sinA * 0.072f;
float matrix[20] = {
a00, a01, a02, 0, 0,
a10, a11, a12, 0, 0,
a20, a21, a22, 0, 0,
0, 0, 0, 1, 0
};
return SkImageFilters::ColorFilter(SkColorFilters::Matrix(matrix, SkColorFilters::Clamp::kNo), input);
},
[&](FilterImpl::Image const& op) -> sk_sp<SkImageFilter> {
auto skia_src_rect = to_skia_rect(op.src_rect);
auto skia_dest_rect = to_skia_rect(op.dest_rect);
auto sampling_options = to_skia_sampling_options(op.scaling_mode);
auto image = sk_image_from_bitmap(op.frame.bitmap(), op.frame.color_space());
return SkImageFilters::Image(move(image), skia_src_rect, skia_dest_rect, sampling_options);
},
[&](FilterImpl::Merge const& op) -> sk_sp<SkImageFilter> {
Vector<sk_sp<SkImageFilter>> filters;
filters.ensure_capacity(op.inputs.size());
for (auto const& input : op.inputs)
filters.unchecked_append(to_optional_skia_image_filter(input));
return SkImageFilters::Merge(filters.data(), filters.size());
},
[&](FilterImpl::Offset const& op) -> sk_sp<SkImageFilter> {
auto input = to_optional_skia_image_filter(op.input);
return SkImageFilters::Offset(op.dx, op.dy, input);
},
[&](FilterImpl::Erode const& op) -> sk_sp<SkImageFilter> {
auto input = to_optional_skia_image_filter(op.input);
return SkImageFilters::Erode(op.radius_x, op.radius_y, input);
},
[&](FilterImpl::Dilate const& op) -> sk_sp<SkImageFilter> {
auto input = to_optional_skia_image_filter(op.input);
return SkImageFilters::Dilate(op.radius_x, op.radius_y, input);
},
[&](FilterImpl::Turbulence const& op) -> sk_sp<SkImageFilter> {
sk_sp<SkShader> turbulence_shader = [&] {
auto skia_size = SkISize::Make(op.tile_stitch_size.width(), op.tile_stitch_size.height());
switch (op.turbulence_type) {
case TurbulenceType::Turbulence:
return SkShaders::MakeTurbulence(op.base_frequency_x, op.base_frequency_y, op.num_octaves, op.seed, &skia_size);
case TurbulenceType::FractalNoise:
return SkShaders::MakeFractalNoise(op.base_frequency_x, op.base_frequency_y, op.num_octaves, op.seed, &skia_size);
}
VERIFY_NOT_REACHED();
}();
return SkImageFilters::Shader(move(turbulence_shader));
});
}
sk_sp<SkImage> sk_image_from_bitmap(Bitmap const& bitmap, ColorSpace const& color_space)

View file

@ -167,7 +167,7 @@ void DisplayListPlayer::execute_impl(
.opacity = effects.opacity,
.compositing_and_blending_operator = effects.blend_mode,
.has_filter = effects.gfx_filter.has_value(),
.filter_id = {},
.filter_data = {},
},
effects.gfx_filter.has_value() ? &effects.gfx_filter.value() : nullptr);
},

View file

@ -436,7 +436,7 @@ struct ApplyBackdropFilter {
Gfx::IntRect backdrop_region;
CornerRadii corner_radii;
bool has_backdrop_filter { false };
FilterResourceId backdrop_filter_id;
DisplayListDataSpan backdrop_filter_data;
[[nodiscard]] Gfx::IntRect bounding_rect() const { return backdrop_region; }
@ -536,7 +536,7 @@ struct ApplyEffects {
float opacity { 1.0f };
Gfx::CompositingAndBlendingOperator compositing_and_blending_operator { Gfx::CompositingAndBlendingOperator::Normal };
bool has_filter { false };
FilterResourceId filter_id;
DisplayListDataSpan filter_data;
bool has_mask_kind { false };
Gfx::MaskKind mask_kind {};

View file

@ -727,8 +727,10 @@ void DisplayListPlayerSkia::apply_backdrop_filter(ApplyBackdropFilter const& com
ScopeGuard guard = [&] { canvas.restore(); };
if (command.has_backdrop_filter) {
auto image_filter = to_skia_image_filter(
active_display_list().resource_storage().filter(command.backdrop_filter_id));
auto filter = Gfx::deserialize_filter(inline_data(command.backdrop_filter_data), [&](u64 image_id) {
return active_display_list().resource_storage().image_frame(ImageFrameResourceId { image_id });
});
auto image_filter = to_skia_image_filter(filter);
canvas.saveLayer(SkCanvas::SaveLayerRec(nullptr, nullptr, image_filter.get(), 0));
canvas.restore();
}
@ -863,8 +865,16 @@ void DisplayListPlayerSkia::apply_effects(ApplyEffects const& command, Gfx::Filt
if (command.compositing_and_blending_operator != Gfx::CompositingAndBlendingOperator::Normal)
paint.setBlender(Gfx::to_skia_blender(command.compositing_and_blending_operator));
if (command.has_filter)
paint.setImageFilter(to_skia_image_filter(filter ? *filter : active_display_list().resource_storage().filter(command.filter_id)));
Optional<Gfx::Filter> deserialized_filter;
if (command.has_filter) {
if (!filter) {
deserialized_filter = Gfx::deserialize_filter(inline_data(command.filter_data), [&](u64 image_id) {
return active_display_list().resource_storage().image_frame(ImageFrameResourceId { image_id });
});
filter = &deserialized_filter.value();
}
paint.setImageFilter(to_skia_image_filter(*filter));
}
if (command.has_mask_kind && command.mask_kind == Gfx::MaskKind::Luminance)
paint.setColorFilter(SkLumaColorFilter::Make());

View file

@ -198,6 +198,18 @@ static DisplayListDataSpan append_path_data(CommandPayloadBuilder<Command>& payl
return payload_builder.append_data(path_data.span(), alignof(u32));
}
template<DisplayListCommand Command>
static DisplayListDataSpan append_filter_data(
CommandPayloadBuilder<Command>& payload_builder,
DisplayListResourceStorage& resource_storage,
Gfx::Filter const& filter)
{
auto filter_data = Gfx::serialize_filter(filter, [&](Gfx::DecodedImageFrame const& frame) {
return resource_storage.add_image_frame(frame).value();
});
return payload_builder.append_data(filter_data, alignof(u32));
}
void DisplayListRecorder::replay_cached_commands(DisplayListCommandSequence const& commands)
{
commands.for_each_command_header([&](DisplayListCommandHeader const& header, ReadonlyBytes) {
@ -555,12 +567,16 @@ void DisplayListRecorder::apply_backdrop_filter(Gfx::IntRect const& backdrop_reg
{
if (backdrop_region.is_empty())
return;
append_command(ApplyBackdropFilter {
.backdrop_region = backdrop_region,
.corner_radii = corner_radii,
.has_backdrop_filter = true,
.backdrop_filter_id = resource_storage().add_filter(backdrop_filter),
});
CommandPayloadBuilder<ApplyBackdropFilter> payload_builder(m_display_list);
auto filter_data = append_filter_data(payload_builder, resource_storage(), backdrop_filter);
append_command(
ApplyBackdropFilter {
.backdrop_region = backdrop_region,
.corner_radii = corner_radii,
.has_backdrop_filter = true,
.backdrop_filter_data = filter_data,
},
payload_builder.inline_data());
}
void DisplayListRecorder::paint_outer_box_shadow(PaintOuterBoxShadow outer_box_shadow)
@ -637,13 +653,19 @@ void DisplayListRecorder::paint_scrollbar(ScrollFrameIndex scroll_frame_index, G
void DisplayListRecorder::apply_effects(float opacity, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator, Optional<Gfx::Filter> filter, Optional<Gfx::MaskKind> mask_kind)
{
append_command(ApplyEffects {
.opacity = opacity,
.compositing_and_blending_operator = compositing_and_blending_operator,
.has_filter = filter.has_value(),
.filter_id = filter.has_value() ? resource_storage().add_filter(filter.value()) : FilterResourceId {},
.has_mask_kind = mask_kind.has_value(),
.mask_kind = mask_kind.value_or({}) });
CommandPayloadBuilder<ApplyEffects> payload_builder(m_display_list);
auto filter_data = filter.has_value()
? append_filter_data(payload_builder, resource_storage(), filter.value())
: DisplayListDataSpan {};
append_command(
ApplyEffects {
.opacity = opacity,
.compositing_and_blending_operator = compositing_and_blending_operator,
.has_filter = filter.has_value(),
.filter_data = filter_data,
.has_mask_kind = mask_kind.has_value(),
.mask_kind = mask_kind.value_or({}) },
payload_builder.inline_data());
}
}

View file

@ -15,7 +15,6 @@ AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, FontResourceId);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, ImageFrameResourceId);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, ExternalContentResourceId);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, VideoFrameResourceId);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, FilterResourceId);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u64, DisplayListResourceId);
}

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Filter.h>
#include <LibGfx/Font/Font.h>
#include <LibWeb/Painting/DisplayList.h>
#include <LibWeb/Painting/DisplayListResourceStorage.h>
@ -49,15 +50,6 @@ VideoFrameResourceId DisplayListResourceStorage::add_video_frame_source(NonnullR
return { id };
}
FilterResourceId DisplayListResourceStorage::add_filter(Gfx::Filter const& filter)
{
auto id = filter.id();
m_filters.ensure(id, [&] {
return filter;
});
return { id };
}
DisplayListResourceId DisplayListResourceStorage::add_display_list(NonnullRefPtr<DisplayList const> display_list)
{
auto id = display_list->id();
@ -67,6 +59,12 @@ DisplayListResourceId DisplayListResourceStorage::add_display_list(NonnullRefPtr
return { id };
}
static ReadonlyBytes inline_data(ReadonlyBytes payload, DisplayListDataSpan span)
{
VERIFY(static_cast<size_t>(span.offset) + span.size <= payload.size());
return payload.slice(span.offset, span.size);
}
void DisplayListResourceStorage::append_referenced_resources_from(
DisplayListResourceStorage const& source,
ReadonlyBytes command_bytes)
@ -86,13 +84,23 @@ void DisplayListResourceStorage::append_referenced_resources_from(
&& command.paint_style.type == DisplayListPaintStyleType::Pattern)
add_display_list(source.display_list(command.paint_style.pattern_tile_display_list_id));
}
if constexpr (requires { command.backdrop_filter_id; }) {
if (command.has_backdrop_filter)
add_filter(source.filter(command.backdrop_filter_id));
if constexpr (requires { command.backdrop_filter_data; }) {
if (command.has_backdrop_filter) {
Gfx::deserialize_filter(inline_data(payload, command.backdrop_filter_data), [&](u64 image_id) {
auto const& frame = source.image_frame(ImageFrameResourceId { image_id });
add_image_frame(frame);
return frame;
});
}
}
if constexpr (requires { command.filter_id; }) {
if (command.has_filter)
add_filter(source.filter(command.filter_id));
if constexpr (requires { command.filter_data; }) {
if (command.has_filter) {
Gfx::deserialize_filter(inline_data(payload, command.filter_data), [&](u64 image_id) {
auto const& frame = source.image_frame(ImageFrameResourceId { image_id });
add_image_frame(frame);
return frame;
});
}
}
if constexpr (requires { command.display_list_id; })
add_display_list(source.display_list(command.display_list_id));

View file

@ -13,7 +13,6 @@
#include <AK/RefPtr.h>
#include <AK/Span.h>
#include <LibGfx/DecodedImageFrame.h>
#include <LibGfx/Filter.h>
#include <LibGfx/Forward.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Painting/DisplayListResourceIds.h>
@ -35,7 +34,6 @@ public:
ImageFrameResourceId add_image_frame(Gfx::DecodedImageFrame const&);
ExternalContentResourceId add_external_content_source(NonnullRefPtr<ExternalContentSource const>);
VideoFrameResourceId add_video_frame_source(NonnullRefPtr<VideoFrameSource const>);
FilterResourceId add_filter(Gfx::Filter const&);
DisplayListResourceId add_display_list(NonnullRefPtr<DisplayList const>);
void append_referenced_resources_from(DisplayListResourceStorage const& source, ReadonlyBytes command_bytes);
@ -43,7 +41,6 @@ public:
Gfx::DecodedImageFrame const& image_frame(ImageFrameResourceId id) const { return m_image_frames.get(id.value()).value(); }
ExternalContentSource const& external_content_source(ExternalContentResourceId id) const { return *m_external_content_sources.get(id.value()).value(); }
VideoFrameSource const& video_frame_source(VideoFrameResourceId id) const { return *m_video_frame_sources.get(id.value()).value(); }
Gfx::Filter const& filter(FilterResourceId id) const { return m_filters.get(id.value()).value(); }
DisplayList const& display_list(DisplayListResourceId id) const { return *m_display_lists.get(id.value()).value(); }
private:
@ -53,7 +50,6 @@ private:
HashMap<u64, Gfx::DecodedImageFrame> m_image_frames;
HashMap<u64, NonnullRefPtr<ExternalContentSource const>> m_external_content_sources;
HashMap<u64, NonnullRefPtr<VideoFrameSource const>> m_video_frame_sources;
HashMap<u64, Gfx::Filter> m_filters;
HashMap<u64, NonnullRefPtr<DisplayList const>> m_display_lists;
};