diff --git a/Libraries/LibGfx/Filter.cpp b/Libraries/LibGfx/Filter.cpp index c846bd8e98..2fe754a230 100644 --- a/Libraries/LibGfx/Filter.cpp +++ b/Libraries/LibGfx/Filter.cpp @@ -4,44 +4,42 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include namespace Gfx { -static Atomic s_next_id { 1 }; - -using Impl = FilterImpl; +static ErrorOr> copy_optional_color_table(Optional bytes) +{ + if (!bytes.has_value()) + return Optional {}; + VERIFY(bytes->size() == 256); + return Optional { 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&& 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 background, Optional foreground, float k1, float k2, float k3, float k4) { - sk_sp background_skia = background.has_value() ? background->m_impl->filter : nullptr; - sk_sp 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 background, Optional foreground, Gfx::CompositingAndBlendingOperator mode) { - sk_sp background_skia = background.has_value() ? background->m_impl->filter : nullptr; - sk_sp 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 input) { - sk_sp 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(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 color, Optional displacement, float scale, ChannelSelector x_channel_selector, ChannelSelector y_channel_selector) { - sk_sp color_skia = color.has_value() ? color->m_impl->filter : nullptr; - sk_sp 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 input) +Filter Filter::drop_shadow(float offset_x, float offset_y, float radius, Gfx::Color color, Optional input) { - sk_sp 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 input) { - sk_sp input_skia = input.has_value() ? input->m_impl->filter : nullptr; - - sk_sp 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 input) { - sk_sp input_skia = input.has_value() ? input->m_impl->filter : nullptr; - - return Filter(Impl::create(SkImageFilters::ColorFilter(SkColorFilters::Matrix(matrix), input_skia))); + Array 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 a, Optional r, Optional g, - Optional b, Optional input) +Filter Filter::color_table(Optional a, Optional r, Optional g, Optional b, Optional 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 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 input) { - sk_sp 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 input) { - sk_sp 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> const& inputs) { - Vector> 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 const& input) { - sk_sp 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 const& input) { - sk_sp 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 input) { - sk_sp 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 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; +using ImageDecoder = Function; + +static void write_color(Stream& stream, Color color) +{ + MUST(stream.write_value(color.value())); +} + +static Color read_color(Stream& stream) +{ + return Color::from_bgra(MUST(stream.read_value())); +} + +static void write_int_rect(Stream& stream, Gfx::IntRect const& rect) +{ + MUST(stream.write_value(rect.x())); + MUST(stream.write_value(rect.y())); + MUST(stream.write_value(rect.width())); + MUST(stream.write_value(rect.height())); +} + +static Gfx::IntRect read_int_rect(Stream& stream) +{ + auto x = MUST(stream.read_value()); + auto y = MUST(stream.read_value()); + auto width = MUST(stream.read_value()); + auto height = MUST(stream.read_value()); + return Gfx::IntRect { x, y, width, height }; +} + +static void write_int_size(Stream& stream, Gfx::IntSize const& size) +{ + MUST(stream.write_value(size.width())); + MUST(stream.write_value(size.height())); +} + +static Gfx::IntSize read_int_size(Stream& stream) +{ + auto width = MUST(stream.read_value()); + auto height = MUST(stream.read_value()); + return Gfx::IntSize { width, height }; +} + +static void write_bytes(Stream& stream, ReadonlyBytes bytes) +{ + VERIFY(bytes.size() <= NumericLimits::max()); + MUST(stream.write_value(bytes.size())); + MUST(stream.write_until_depleted(bytes)); +} + +static ByteBuffer read_bytes(Stream& stream) +{ + auto size = MUST(stream.read_value()); + 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 +static void encode_optional_filter(Stream& stream, Optional const& filter, ImageEncoder const& encode_image) +{ + MUST(stream.write_value(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 const& bytes) { + MUST(stream.write_value(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(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::max()); + MUST(stream.write_value(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 decode_optional_filter(Stream&, ImageDecoder const&); + +static Filter decode_filter(Stream& stream, ImageDecoder const& decode_image) +{ + auto operation_type = MUST(stream.read_value()); + 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()); + auto k2 = MUST(stream.read_value()); + auto k3 = MUST(stream.read_value()); + auto k4 = MUST(stream.read_value()); + 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()); + return Filter::blend(background, foreground, mode); + } + case FilterImpl::OperationType::Flood: { + auto color = read_color(stream); + auto opacity = MUST(stream.read_value()); + 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()); + auto x_channel_selector = MUST(stream.read_value()); + auto y_channel_selector = MUST(stream.read_value()); + return Filter::displacement_map(color, displacement, scale, x_channel_selector, y_channel_selector); + } + case FilterImpl::OperationType::DropShadow: { + auto offset_x = MUST(stream.read_value()); + auto offset_y = MUST(stream.read_value()); + auto radius = MUST(stream.read_value()); + 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()); + auto radius_y = MUST(stream.read_value()); + 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()); + auto amount = MUST(stream.read_value()); + auto input = decode_optional_filter(stream, decode_image); + return Filter::color(type, amount, input); + } + case FilterImpl::OperationType::ColorMatrix: { + Array matrix_values; + for (auto& value : matrix_values) + value = MUST(stream.read_value()); + 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 { + auto has_value = MUST(stream.read_value()); + if (!has_value) + return {}; + auto bytes = read_bytes(stream); + VERIFY(bytes.size() == 256); + return Optional { 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(a->bytes()) : Optional {}, + r.has_value() ? Optional(r->bytes()) : Optional {}, + g.has_value() ? Optional(g->bytes()) : Optional {}, + b.has_value() ? Optional(b->bytes()) : Optional {}, + input); + } + case FilterImpl::OperationType::Saturate: { + auto value = MUST(stream.read_value()); + auto input = decode_optional_filter(stream, decode_image); + return Filter::saturate(value, input); + } + case FilterImpl::OperationType::HueRotate: { + auto angle_degrees = MUST(stream.read_value()); + 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()); + 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()); + return Filter::image(frame, src_rect, dest_rect, scaling_mode); + } + case FilterImpl::OperationType::Merge: { + Vector> inputs; + auto size = MUST(stream.read_value()); + 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()); + auto dy = MUST(stream.read_value()); + 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()); + auto radius_y = MUST(stream.read_value()); + 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()); + auto radius_y = MUST(stream.read_value()); + 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()); + auto base_frequency_x = MUST(stream.read_value()); + auto base_frequency_y = MUST(stream.read_value()); + auto num_octaves = MUST(stream.read_value()); + auto seed = MUST(stream.read_value()); + 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 decode_optional_filter(Stream& stream, ImageDecoder const& decode_image) +{ + auto has_value = MUST(stream.read_value()); + if (!has_value) + return {}; + return decode_filter(stream, decode_image); +} + +} + +ByteBuffer serialize_filter(Filter const& filter, Function 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 const& decode_image) +{ + FixedMemoryStream stream { bytes }; + auto filter = decode_filter(stream, decode_image); + VERIFY(stream.is_eof()); + return filter; } } diff --git a/Libraries/LibGfx/Filter.h b/Libraries/LibGfx/Filter.h index 2414763700..eb94a049a1 100644 --- a/Libraries/LibGfx/Filter.h +++ b/Libraries/LibGfx/Filter.h @@ -6,10 +6,14 @@ #pragma once +#include +#include +#include #include #include #include #include +#include #include #include #include @@ -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&&); - u64 m_id { 0 }; NonnullOwnPtr m_impl; }; +ByteBuffer serialize_filter(Filter const&, Function const& encode_image); +Filter deserialize_filter(ReadonlyBytes, Function const& decode_image); + } diff --git a/Libraries/LibGfx/FilterImpl.h b/Libraries/LibGfx/FilterImpl.h index 325fb504c8..2cbe886beb 100644 --- a/Libraries/LibGfx/FilterImpl.h +++ b/Libraries/LibGfx/FilterImpl.h @@ -6,23 +6,182 @@ #pragma once +#include +#include #include -#include -#include +#include +#include +#include +#include namespace Gfx { struct FilterImpl { - sk_sp filter; + struct Arithmetic { + Optional background; + Optional foreground; + float k1 { 0.0f }; + float k2 { 0.0f }; + float k3 { 0.0f }; + float k4 { 0.0f }; + }; - static NonnullOwnPtr create(sk_sp filter) + struct Compose { + Filter outer; + Filter inner; + }; + + struct Blend { + Optional background; + Optional foreground; + Gfx::CompositingAndBlendingOperator mode { Gfx::CompositingAndBlendingOperator::Normal }; + }; + + struct Flood { + Gfx::Color color; + float opacity { 1.0f }; + }; + + struct DisplacementMap { + Optional color; + Optional 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 input; + }; + + struct Blur { + float radius_x { 0.0f }; + float radius_y { 0.0f }; + Optional input; + }; + + struct ColorFilter { + ColorFilterType type { ColorFilterType::Brightness }; + float amount { 0.0f }; + Optional input; + }; + + struct ColorMatrix { + Array matrix; + Optional input; + }; + + struct ColorTable { + Optional a; + Optional r; + Optional g; + Optional b; + Optional input; + }; + + struct Saturate { + float value { 0.0f }; + Optional input; + }; + + struct HueRotate { + float angle_degrees { 0.0f }; + Optional input; + }; + + struct Image { + Gfx::DecodedImageFrame frame; + Gfx::IntRect src_rect; + Gfx::IntRect dest_rect; + Gfx::ScalingMode scaling_mode { Gfx::ScalingMode::NearestNeighbor }; + }; + + struct Merge { + Vector> inputs; + }; + + struct Offset { + float dx { 0.0f }; + float dy { 0.0f }; + Optional input; + }; + + struct Erode { + float radius_x { 0.0f }; + float radius_y { 0.0f }; + Optional input; + }; + + struct Dilate { + float radius_x { 0.0f }; + float radius_y { 0.0f }; + Optional 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 create(Operation operation) { - return adopt_own(*new FilterImpl(move(filter))); + return adopt_own(*new FilterImpl(move(operation))); } NonnullOwnPtr clone() const { - return adopt_own(*new FilterImpl(filter)); + return adopt_own(*new FilterImpl(operation)); } }; diff --git a/Libraries/LibGfx/SkiaUtils.cpp b/Libraries/LibGfx/SkiaUtils.cpp index 2242278402..9856770b74 100644 --- a/Libraries/LibGfx/SkiaUtils.cpp +++ b/Libraries/LibGfx/SkiaUtils.cpp @@ -5,17 +5,23 @@ */ #include +#include #include #include +#include #include #include #include #include #include +#include #include #include #include #include +#include +#include +#include #include namespace Gfx { @@ -27,7 +33,253 @@ SkPath to_skia_path(Path const& path) sk_sp to_skia_image_filter(Gfx::Filter const& filter) { - return filter.impl().filter; + auto to_optional_skia_image_filter = [](Optional const& input) -> sk_sp { + if (!input.has_value()) + return nullptr; + return to_skia_image_filter(input.value()); + }; + + return filter.impl().operation.visit( + [&](FilterImpl::Arithmetic const& op) -> sk_sp { + 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 { + 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 { + 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 { + auto color = to_skia_color(op.color); + color = SkColorSetA(color, static_cast(op.opacity * 255)); + return SkImageFilters::Shader(SkShaders::Color(color)); + }, + [&](FilterImpl::DisplacementMap const& op) -> sk_sp { + 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 { + 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 { + 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 { + auto input = to_optional_skia_image_filter(op.input); + sk_sp 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 { + auto input = to_optional_skia_image_filter(op.input); + return SkImageFilters::ColorFilter(SkColorFilters::Matrix(op.matrix.data()), input); + }, + [&](FilterImpl::ColorTable const& op) -> sk_sp { + 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 { + 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 { + 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 { + 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 { + Vector> 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 { + auto input = to_optional_skia_image_filter(op.input); + return SkImageFilters::Offset(op.dx, op.dy, input); + }, + [&](FilterImpl::Erode const& op) -> sk_sp { + 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 { + 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 { + sk_sp 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 sk_image_from_bitmap(Bitmap const& bitmap, ColorSpace const& color_space) diff --git a/Libraries/LibWeb/Painting/DisplayList.cpp b/Libraries/LibWeb/Painting/DisplayList.cpp index ddb5de5327..45edb900d7 100644 --- a/Libraries/LibWeb/Painting/DisplayList.cpp +++ b/Libraries/LibWeb/Painting/DisplayList.cpp @@ -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); }, diff --git a/Libraries/LibWeb/Painting/DisplayListCommand.h b/Libraries/LibWeb/Painting/DisplayListCommand.h index cdddd14d8e..2513058d0c 100644 --- a/Libraries/LibWeb/Painting/DisplayListCommand.h +++ b/Libraries/LibWeb/Painting/DisplayListCommand.h @@ -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 {}; diff --git a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp index b65f5fa291..2c90c1a78d 100644 --- a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp +++ b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp @@ -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 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()); diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index b15afd875e..a3ac2a3dbd 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -198,6 +198,18 @@ static DisplayListDataSpan append_path_data(CommandPayloadBuilder& payl return payload_builder.append_data(path_data.span(), alignof(u32)); } +template +static DisplayListDataSpan append_filter_data( + CommandPayloadBuilder& 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 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 filter, Optional 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 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()); } } diff --git a/Libraries/LibWeb/Painting/DisplayListResourceIds.h b/Libraries/LibWeb/Painting/DisplayListResourceIds.h index d4f63694dc..18f6ad80f8 100644 --- a/Libraries/LibWeb/Painting/DisplayListResourceIds.h +++ b/Libraries/LibWeb/Painting/DisplayListResourceIds.h @@ -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); } diff --git a/Libraries/LibWeb/Painting/DisplayListResourceStorage.cpp b/Libraries/LibWeb/Painting/DisplayListResourceStorage.cpp index 89a221837b..594d566fe9 100644 --- a/Libraries/LibWeb/Painting/DisplayListResourceStorage.cpp +++ b/Libraries/LibWeb/Painting/DisplayListResourceStorage.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -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 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(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)); diff --git a/Libraries/LibWeb/Painting/DisplayListResourceStorage.h b/Libraries/LibWeb/Painting/DisplayListResourceStorage.h index 1a1af8849a..5de36b47c3 100644 --- a/Libraries/LibWeb/Painting/DisplayListResourceStorage.h +++ b/Libraries/LibWeb/Painting/DisplayListResourceStorage.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -35,7 +34,6 @@ public: ImageFrameResourceId add_image_frame(Gfx::DecodedImageFrame const&); ExternalContentResourceId add_external_content_source(NonnullRefPtr); VideoFrameResourceId add_video_frame_source(NonnullRefPtr); - FilterResourceId add_filter(Gfx::Filter const&); DisplayListResourceId add_display_list(NonnullRefPtr); 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 m_image_frames; HashMap> m_external_content_sources; HashMap> m_video_frame_sources; - HashMap m_filters; HashMap> m_display_lists; };