/* * Copyright (c) 2025, Lucien Fiorini * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include namespace Gfx { 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_impl(other.m_impl->clone()) { } Filter& Filter::operator=(Filter const& other) { 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_impl(move(impl)) { } FilterImpl const& Filter::impl() const { return *m_impl; } Filter Filter::arithmetic(Optional background, Optional foreground, float k1, float k2, float k3, float k4) { 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) { return Filter(FilterImpl::create(FilterImpl::Compose { .outer = outer, .inner = inner, })); } Filter Filter::blend(Optional background, Optional foreground, Gfx::CompositingAndBlendingOperator mode) { 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) { 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) { 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) { 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) { 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) { return Filter(FilterImpl::create(FilterImpl::ColorFilter { .type = type, .amount = amount, .input = input.copy(), })); } Filter Filter::color_matrix(float matrix[20], Optional input) { 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) { 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) { return Filter(FilterImpl::create(FilterImpl::Saturate { .value = value, .input = input.copy(), })); } Filter Filter::hue_rotate(float angle_degrees, Optional input) { 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) { 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) { return Filter(FilterImpl::create(FilterImpl::Merge { .inputs = inputs, })); } Filter Filter::erode(float radius_x, float radius_y, Optional const& input) { 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) { 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) { 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) { 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, })); } 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; } } namespace IPC { static ErrorOr encode_decoded_image_frame(Encoder& encoder, u64 id, Gfx::DecodedImageFrame const& frame) { auto bitmap = frame.bitmap().to_shareable_bitmap(); if (!bitmap.is_valid()) return Error::from_string_literal("IPC encode: failed to create shareable bitmap for filter image"); TRY(encoder.encode(id)); TRY(encoder.encode(bitmap)); TRY(encoder.encode(frame.color_space())); return {}; } static ErrorOr decode_decoded_image_frame(Decoder& decoder) { auto bitmap = TRY(decoder.decode()); if (!bitmap.is_valid() || !bitmap.bitmap()) return Error::from_string_literal("IPC decode: invalid filter image bitmap"); auto color_space = TRY(decoder.decode()); return Gfx::DecodedImageFrame { *bitmap.bitmap(), move(color_space) }; } template<> ErrorOr encode(Encoder& encoder, Gfx::Filter const& filter) { HashMap images; auto filter_data = Gfx::serialize_filter(filter, [&](Gfx::DecodedImageFrame const& frame) { images.ensure(frame.id(), [&] { return frame; }); return frame.id(); }); TRY(encoder.encode(filter_data)); TRY(encoder.encode_size(images.size())); for (auto const& image : images) TRY(encode_decoded_image_frame(encoder, image.key, image.value)); return {}; } template<> ErrorOr decode(Decoder& decoder) { auto filter_data = TRY(decoder.decode()); auto image_count = TRY(decoder.decode_size()); HashMap images; TRY(images.try_ensure_capacity(image_count)); for (size_t i = 0; i < image_count; ++i) { auto id = TRY(decoder.decode()); auto frame = TRY(decode_decoded_image_frame(decoder)); TRY(images.try_set(id, move(frame))); } return Gfx::deserialize_filter(filter_data.bytes(), [&](u64 image_id) { auto image = images.get(image_id); VERIFY(image.has_value()); return image.value(); }); } }