use super::{paint_composition_brush, ServerCompositionSurfaceBrush};
use crate::media::immutable::ImmutableSolidColorBrush;
use crate::media::imaging::BitmapBlendingMode;
use crate::media::{BoxShadows, Color, ColorMatrix, ColorMatrixAlphaMode, EffectExtensions, IEffect, ImmutableBlurEffect};
use crate::platform::{AlphaFormat, IDrawingContextImpl, PixelFormat, PixelFormats};
use crate::rendering::composition::effects::{
blur_amount_to_radius, BlendEffectMode, EffectColor, EffectGraph, EffectMatrix, EffectNode, EffectNodeKind,
EffectScalar, PixelOp,
};
use crate::rendering::composition::generated::{ServerCompositionBrushHooks, ServerCompositionBrushProps};
use crate::rendering::composition::server::{
impl_server_render_resource, IAnimatedServerObject, IServerObject, IServerRenderResource,
IServerRenderResourceObserver, ServerCompositionPropertySet, ServerCompositor, ServerObjectId,
ServerRenderResource,
};
use crate::rendering::composition::transport::BatchStreamReader;
use crate::{Matrix, PixelSize, Rect, RoundedRect, Thickness, Vector};
use std::any::{Any, TypeId};
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::time::Duration;
pub(crate) struct EffectBrushChanges {
pub(crate) properties: Option<ServerObjectId>,
pub(crate) sources: Option<Vec<(String, Option<ServerObjectId>)>>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum LayerPhase {
Before,
After,
}
fn is_empty(rect: &Rect) -> bool {
rect.width <= 0.0 || rect.height <= 0.0
}
pub struct ServerCompositionEffectBrush {
this: Weak<ServerCompositionEffectBrush>,
base: ServerRenderResource,
props: ServerCompositionBrushProps,
graph: Arc<EffectGraph>,
properties: RefCell<Option<Rc<ServerCompositionPropertySet>>>,
sources: RefCell<Vec<(String, Option<Rc<dyn IServerObject>>)>>,
painting: Cell<bool>,
}
impl ServerCompositionEffectBrush {
pub(crate) fn new(compositor: &Rc<ServerCompositor>, graph: Arc<EffectGraph>) -> Rc<ServerCompositionEffectBrush> {
let brush = Rc::new_cyclic(|this: &Weak<ServerCompositionEffectBrush>| ServerCompositionEffectBrush {
this: this.clone(),
base: ServerRenderResource::new(
compositor,
this.clone() as Weak<dyn IAnimatedServerObject>,
this.clone() as Weak<dyn IServerRenderResource>,
),
props: ServerCompositionBrushProps::new(),
graph,
properties: RefCell::new(None),
sources: RefCell::new(Vec::new()),
painting: Cell::new(false),
});
brush.base.object().activate();
brush
}
pub fn brush_props(&self) -> &ServerCompositionBrushProps {
&self.props
}
pub fn is_disposed(&self) -> bool {
self.base.is_disposed()
}
pub fn is_loaded(&self) -> bool {
self.graph.is_loaded()
}
pub fn properties(&self) -> Option<Rc<ServerCompositionPropertySet>> {
self.properties.borrow().clone()
}
pub fn source(&self, name: &str) -> Option<Rc<dyn IServerObject>> {
self.sources.borrow().iter().find(|(existing, _)| existing == name).and_then(|(_, source)| source.clone())
}
fn subscription_key(&self) -> usize {
self.this.as_ptr() as *const () as usize
}
fn observer(&self) -> Option<Rc<dyn IServerRenderResourceObserver>> {
self.this.upgrade().map(|this| this as Rc<dyn IServerRenderResourceObserver>)
}
fn set_properties(&self, properties: Option<Rc<ServerCompositionPropertySet>>) {
if let Some(old) = self.properties.borrow().as_ref() {
old.changed().remove(self.subscription_key());
}
if let Some(new) = &properties {
let weak = self.this.clone();
new.changed().add(
self.subscription_key(),
Rc::new(move || {
if let Some(brush) = weak.upgrade() {
brush.base.core().invalidated(&*brush);
}
}),
);
}
*self.properties.borrow_mut() = properties;
}
fn is_self(&self, source: &Rc<dyn IServerObject>) -> bool {
std::ptr::addr_eq(Rc::as_ptr(source), self.this.as_ptr())
}
fn set_sources(&self, sources: Vec<(String, Option<Rc<dyn IServerObject>>)>) {
let old = std::mem::replace(&mut *self.sources.borrow_mut(), sources);
let Some(observer) = self.observer() else { return };
let resources = |sources: &[(String, Option<Rc<dyn IServerObject>>)]| -> Vec<Rc<dyn IServerRenderResource>> {
sources
.iter()
.filter_map(|(_, source)| source.clone())
.filter(|source| !self.is_self(source))
.filter_map(|source| source.as_render_resource())
.collect()
};
for resource in resources(old.as_slice()) {
resource.remove_observer(&observer);
}
let new = resources(self.sources.borrow().as_slice());
for resource in new {
resource.add_observer(&observer);
}
}
fn scalar(&self, value: &EffectScalar) -> f64 {
if let (Some(key), Some(properties)) = (&value.key, self.properties.borrow().as_ref()) {
if let Some(current) = properties.get_value(key).try_cast::<f64>() {
return current;
}
}
value.value as f64
}
fn color(&self, value: &EffectColor) -> Color {
if let (Some(key), Some(properties)) = (&value.key, self.properties.borrow().as_ref()) {
if let Some(current) = properties.get_value(key).try_cast::<Color>() {
return current;
}
}
value.value
}
fn blur_radius(&self, amount: &EffectScalar) -> f64 {
blur_amount_to_radius(self.scalar(amount))
}
fn unit(&self, value: &EffectScalar) -> f64 {
let value = self.scalar(value);
if value.is_nan() {
return 0.0;
}
value.clamp(0.0, 1.0)
}
fn push_blur(canvas: &mut dyn IDrawingContextImpl, rect: Rect, radius: f64) -> bool {
if radius <= 0.0 {
return false;
}
match canvas.as_drawing_context_impl_with_effects() {
Some(effects) => {
effects.push_effect(Some(rect), &ImmutableBlurEffect::new(radius));
true
}
None => false,
}
}
fn pop_blur(canvas: &mut dyn IDrawingContextImpl, pushed: bool) {
if pushed {
if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
effects.pop_effect();
}
}
}
fn blur_is_pushed(canvas: &mut dyn IDrawingContextImpl, radius: f64) -> bool {
radius > 0.0 && canvas.as_drawing_context_impl_with_effects().is_some()
}
fn paint_node_with_opacity(&self, node: &EffectNode, canvas: &mut dyn IDrawingContextImpl, rect: Rect, opacity: f64) {
if opacity <= 0.0 {
return;
}
if opacity < 1.0 {
canvas.push_opacity(opacity, Some(rect));
}
self.paint_node(node, canvas, rect);
if opacity < 1.0 {
canvas.pop_opacity();
}
}
fn blur_reach(radius: f64) -> f64 {
if radius <= 0.0 {
return 0.0;
}
let blur = ImmutableBlurEffect::new(radius);
let padding = EffectExtensions::get_effect_output_padding(Some(&blur as &dyn IEffect));
padding.left.max(padding.top).max(padding.right).max(padding.bottom)
}
fn matrix(&self, matrix: &EffectMatrix) -> ColorMatrix {
match matrix {
EffectMatrix::Fixed(matrix) => *matrix,
EffectMatrix::Saturation(saturation) => ColorMatrix::saturation(self.scalar(saturation) as f32),
EffectMatrix::Tint(color) => {
let color = self.color(color);
ColorMatrix::tint([color.r, color.g, color.b, color.a].map(|channel| channel as f32 / 255.0))
}
}
}
fn node_color(&self, node: &EffectNode) -> [f32; 4] {
let EffectNodeKind::ColorSource(color) = &node.kind else { return [0.0; 4] };
let color = self.color(color);
let alpha = color.a as f32 / 255.0;
[color.r as f32 / 255.0 * alpha, color.g as f32 / 255.0 * alpha, color.b as f32 / 255.0 * alpha, alpha]
}
fn arithmetic_matrix(
&self,
multiply: &EffectScalar,
source1: &EffectScalar,
source2: &EffectScalar,
offset: &EffectScalar,
image_is_source1: bool,
color: &EffectNode,
) -> ColorMatrix {
let c = self.node_color(color);
let k1 = self.scalar(multiply) as f32;
let (k_image, k_color) = if image_is_source1 {
(self.scalar(source1) as f32, self.scalar(source2) as f32)
} else {
(self.scalar(source2) as f32, self.scalar(source1) as f32)
};
let k4 = self.scalar(offset) as f32;
ColorMatrix::scale_and_offset(c.map(|c| k1 * c + k_image), c.map(|c| k_color * c + k4))
}
fn push_blend(canvas: &mut dyn IDrawingContextImpl, rect: Rect, mode: BitmapBlendingMode) -> bool {
match canvas.as_drawing_context_impl_with_layer_effects() {
Some(layers) => {
layers.push_blend_layer(rect, mode);
true
}
None => false,
}
}
fn pop_blend(canvas: &mut dyn IDrawingContextImpl, pushed: bool) {
if pushed {
if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
layers.pop_blend_layer();
}
}
}
fn push_matrix(canvas: &mut dyn IDrawingContextImpl, rect: Rect, matrix: &ColorMatrix, alpha_mode: ColorMatrixAlphaMode) -> bool {
if matrix.is_identity() {
return false;
}
match canvas.as_drawing_context_impl_with_layer_effects() {
Some(layers) => {
layers.push_color_matrix_layer(rect, matrix, alpha_mode);
true
}
None => false,
}
}
fn pop_matrix(canvas: &mut dyn IDrawingContextImpl, pushed: bool) {
if pushed {
if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
layers.pop_color_matrix_layer();
}
}
}
fn paint_node(&self, node: &EffectNode, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
match &node.kind {
EffectNodeKind::Parameter(name) => {
if let Some(source) = self.source(name).filter(|source| !self.is_self(source)) {
paint_composition_brush(canvas, &source, rect);
}
}
EffectNodeKind::GaussianBlur { amount, hard, source } => {
let radius = self.blur_radius(amount);
if *hard && Self::blur_is_pushed(canvas, radius) {
let reach = Self::blur_reach(radius);
let wide = rect.inflate(reach);
canvas.push_clip(rect);
let pushed = Self::push_blur(canvas, wide, radius);
self.paint_node(source, canvas, wide);
Self::pop_blur(canvas, pushed);
canvas.pop_clip();
} else {
let pushed = Self::push_blur(canvas, rect, radius);
self.paint_node(source, canvas, rect);
Self::pop_blur(canvas, pushed);
}
}
EffectNodeKind::Opacity { opacity, source } => {
self.paint_node_with_opacity(source, canvas, rect, self.unit(opacity));
}
EffectNodeKind::CrossFade { weight, source1, source2 } => {
let weight = self.unit(weight);
if weight <= 0.0 || weight >= 1.0 {
self.paint_node_with_opacity(source1, canvas, rect, 1.0 - weight);
self.paint_node_with_opacity(source2, canvas, rect, weight);
return;
}
canvas.push_layer(rect);
self.paint_node_with_opacity(source1, canvas, rect, 1.0 - weight);
let pushed = Self::push_blend(canvas, rect, BitmapBlendingMode::Plus);
self.paint_node_with_opacity(source2, canvas, rect, weight);
Self::pop_blend(canvas, pushed);
canvas.pop_layer();
}
EffectNodeKind::Composite { mode, sources } => {
if *mode == BitmapBlendingMode::SourceOver {
for source in sources {
self.paint_node(source, canvas, rect);
}
return;
}
canvas.push_layer(rect);
for (index, source) in sources.iter().enumerate() {
let pushed = index > 0 && Self::push_blend(canvas, rect, *mode);
self.paint_node(source, canvas, rect);
Self::pop_blend(canvas, pushed);
}
canvas.pop_layer();
}
EffectNodeKind::ColorSource(color) => {
let color = self.color(color);
if color.a > 0 {
let brush = ImmutableSolidColorBrush::new(color);
canvas.draw_rectangle(Some(&brush), None, RoundedRect::from_rect(rect), &BoxShadows::default());
}
}
EffectNodeKind::Blend { mode, background, foreground } => {
canvas.push_layer(rect);
self.paint_node(background, canvas, rect);
let pushed = Self::push_blend(canvas, rect, *mode);
self.paint_node(foreground, canvas, rect);
Self::pop_blend(canvas, pushed);
canvas.pop_layer();
}
EffectNodeKind::ColorMatrix { matrix, alpha_mode, source } => {
let matrix = self.matrix(matrix);
let pushed = Self::push_matrix(canvas, rect, &matrix, *alpha_mode);
self.paint_node(source, canvas, rect);
Self::pop_matrix(canvas, pushed);
}
EffectNodeKind::AlphaMask { source, mask } => {
canvas.push_layer(rect);
self.paint_node(source, canvas, rect);
let pushed = Self::push_blend(canvas, rect, BitmapBlendingMode::DestinationIn);
if pushed {
self.paint_node(mask, canvas, rect);
}
Self::pop_blend(canvas, pushed);
canvas.pop_layer();
}
EffectNodeKind::Border { tile, source } => {
let surface = match &source.kind {
EffectNodeKind::Parameter(name) if *tile => self.source(name),
_ => None,
};
match surface.as_ref().and_then(|surface| surface.as_any().downcast_ref::<ServerCompositionSurfaceBrush>()) {
Some(surface) => surface.paint_tiled(canvas, rect),
None => self.paint_node(source, canvas, rect),
}
}
EffectNodeKind::ArithmeticWithColor { multiply, source1, source2, offset, image_is_source1, image, color } => {
let matrix = self.arithmetic_matrix(multiply, source1, source2, offset, *image_is_source1, color);
let pushed = Self::push_matrix(canvas, rect, &matrix, ColorMatrixAlphaMode::Straight);
self.paint_node(image, canvas, rect);
Self::pop_matrix(canvas, pushed);
}
EffectNodeKind::Shadow { amount, color, source } => {
let radius = self.blur_radius(amount);
let matrix = shadow_matrix(self.color(color));
let blurred = Self::push_blur(canvas, rect, radius);
let colored = Self::push_matrix(canvas, rect, &matrix, ColorMatrixAlphaMode::Premultiplied);
self.paint_node(source, canvas, rect);
Self::pop_matrix(canvas, colored);
Self::pop_blur(canvas, blurred);
}
EffectNodeKind::Pixels { op, sources } => self.paint_pixels(op, sources, canvas, rect),
EffectNodeKind::SceneLighting { ambient, diffuse, specular, shine } => {
let material = crate::rendering::composition::server::LightingMaterial {
ambient: self.scalar(ambient),
diffuse: self.scalar(diffuse),
specular: self.scalar(specular),
shine: self.scalar(shine),
};
crate::rendering::composition::server::paint_scene_lighting(canvas, rect, &material);
}
}
}
fn render_node_pixels(&self, node: &EffectNode, canvas: &dyn IDrawingContextImpl, rect: Rect, size: PixelSize) -> Option<Vec<u8>> {
let compositor = self.base.compositor()?;
let transform = Matrix::create_translation(-rect.x, -rect.y)
* Matrix::create_scale(size.width as f64 / rect.width, size.height as f64 / rect.height);
let _ = canvas;
compositor.render_interface().with_platform_render_interface(|render_interface| {
let bitmap = render_interface.create_render_target_bitmap(size, Vector::new(96.0, 96.0));
{
let mut context = bitmap.create_drawing_context();
context.set_transform(transform);
self.paint_node(node, &mut *context, rect);
context.dispose();
}
let framebuffer = bitmap.lock();
let (width, height) = (size.width as usize, size.height as usize);
let row_bytes = framebuffer.row_bytes() as usize;
let bgra = framebuffer.format() == PixelFormat::BGRA8888;
let premultiplied = framebuffer.alpha_format() != AlphaFormat::Unpremul;
let mut rgba = vec![0u8; width * height * 4];
framebuffer.with_data(&mut |data| {
for y in 0..height {
for x in 0..width {
let at = y * row_bytes + x * 4;
let Some(pixel) = data.get(at..at + 4) else { continue };
let (mut r, mut g, mut b, a) =
if bgra { (pixel[2], pixel[1], pixel[0], pixel[3]) } else { (pixel[0], pixel[1], pixel[2], pixel[3]) };
if !premultiplied {
let scale = |c: u8| ((u32::from(c) * u32::from(a) + 127) / 255) as u8;
(r, g, b) = (scale(r), scale(g), scale(b));
}
rgba[(y * width + x) * 4..(y * width + x) * 4 + 4].copy_from_slice(&[r, g, b, a]);
}
}
});
framebuffer.dispose();
rgba
})
}
fn paint_pixels(&self, op: &PixelOp, sources: &[EffectNode], canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
if sources.is_empty() || is_empty(&rect) {
return;
}
let transform = canvas.transform();
let scale_x = (transform.m11 * transform.m11 + transform.m12 * transform.m12).sqrt().max(1e-6);
let scale_y = (transform.m21 * transform.m21 + transform.m22 * transform.m22).sqrt().max(1e-6);
let size = PixelSize::new(
((rect.width * scale_x).ceil() as i32).clamp(1, 4096),
((rect.height * scale_y).ceil() as i32).clamp(1, 4096),
);
let mut images = Vec::with_capacity(sources.len());
for source in sources {
match self.render_node_pixels(source, canvas, rect, size) {
Some(pixels) => images.push(pixels),
None => return,
}
}
let mut images = images.into_iter();
let Some(mut result) = images.next() else { return };
let width = size.width as usize;
for (index, other) in images.enumerate() {
match op {
PixelOp::Blend(mode) => blend_pixels(*mode, &mut result, &other, width),
PixelOp::Arithmetic { multiply, source1, source2, offset } => {
let k = [self.scalar(multiply), self.scalar(source1), self.scalar(source2), self.scalar(offset)];
arithmetic_pixels(k.map(|k| k as f32), &mut result, &other);
}
PixelOp::MaskInvert => mask_invert_pixels(&mut result, &other),
}
if !matches!(op, PixelOp::MaskInvert) && index == 0 {
break;
}
}
let Some(compositor) = self.base.compositor() else { return };
let bitmap = compositor.render_interface().with_platform_render_interface(|render_interface| {
render_interface.load_bitmap_from_pixels(
PixelFormats::RGBA8888,
AlphaFormat::Premul,
&result,
size,
Vector::new(96.0, 96.0),
size.width * 4,
)
});
if let Some(bitmap) = bitmap {
let source = Rect::new(0.0, 0.0, size.width as f64, size.height as f64);
canvas.draw_bitmap(&*bitmap, 1.0, source, rect);
}
}
pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
let Some(root) = self.graph.root.as_ref() else { return };
let opacity = self.props.opacity();
if opacity <= 0.0 || is_empty(&rect) || self.painting.replace(true) {
return;
}
if opacity != 1.0 {
canvas.push_opacity(opacity, Some(rect));
}
self.paint_node(root, canvas, rect);
if opacity != 1.0 {
canvas.pop_opacity();
}
self.painting.set(false);
}
fn paint_around_layer(
&self,
node: &EffectNode,
path: &[usize],
canvas: &mut dyn IDrawingContextImpl,
rect: Rect,
phase: LayerPhase,
) {
let Some((&index, rest)) = path.split_first() else {
return;
};
let before = phase == LayerPhase::Before;
let has_layers = canvas.as_drawing_context_impl_with_layer_effects().is_some();
match &node.kind {
EffectNodeKind::Parameter(_) | EffectNodeKind::ColorSource(_) | EffectNodeKind::SceneLighting { .. } => {}
EffectNodeKind::GaussianBlur { amount, source, .. } => {
let radius = self.blur_radius(amount);
match phase {
LayerPhase::Before => {
Self::push_blur(canvas, rect, radius);
self.paint_around_layer(source, rest, canvas, rect, phase);
}
LayerPhase::After => {
self.paint_around_layer(source, rest, canvas, rect, phase);
let pushed = Self::blur_is_pushed(canvas, radius);
Self::pop_blur(canvas, pushed);
}
}
}
EffectNodeKind::Opacity { opacity, source } => {
let opacity = self.unit(opacity);
self.wrap_layer_in_opacity(source, rest, canvas, rect, phase, opacity);
}
EffectNodeKind::CrossFade { weight, source1, source2 } => {
let weight = self.unit(weight);
if index == 0 {
self.wrap_layer_in_opacity(source1, rest, canvas, rect, phase, 1.0 - weight);
if phase == LayerPhase::After {
self.paint_node_with_opacity(source2, canvas, rect, weight);
}
} else {
if phase == LayerPhase::Before {
self.paint_node_with_opacity(source1, canvas, rect, 1.0 - weight);
}
self.wrap_layer_in_opacity(source2, rest, canvas, rect, phase, weight);
}
}
EffectNodeKind::Composite { mode, sources } => {
let Some(layer) = sources.get(index) else { return };
let blended = *mode != BitmapBlendingMode::SourceOver && has_layers;
if before {
if blended {
canvas.push_layer(rect);
}
for (position, source) in sources[..index].iter().enumerate() {
let pushed = blended && position > 0 && Self::push_blend(canvas, rect, *mode);
self.paint_node(source, canvas, rect);
Self::pop_blend(canvas, pushed);
}
if blended && index > 0 {
Self::push_blend(canvas, rect, *mode);
}
self.paint_around_layer(layer, rest, canvas, rect, phase);
} else {
self.paint_around_layer(layer, rest, canvas, rect, phase);
Self::pop_blend(canvas, blended && index > 0);
for source in &sources[index + 1..] {
let pushed = blended && Self::push_blend(canvas, rect, *mode);
self.paint_node(source, canvas, rect);
Self::pop_blend(canvas, pushed);
}
if blended {
canvas.pop_layer();
}
}
}
EffectNodeKind::Blend { mode, background, foreground } => {
if index == 0 {
if before {
canvas.push_layer(rect);
self.paint_around_layer(background, rest, canvas, rect, phase);
} else {
self.paint_around_layer(background, rest, canvas, rect, phase);
let pushed = Self::push_blend(canvas, rect, *mode);
self.paint_node(foreground, canvas, rect);
Self::pop_blend(canvas, pushed);
canvas.pop_layer();
}
} else if before {
canvas.push_layer(rect);
self.paint_node(background, canvas, rect);
Self::push_blend(canvas, rect, *mode);
self.paint_around_layer(foreground, rest, canvas, rect, phase);
} else {
self.paint_around_layer(foreground, rest, canvas, rect, phase);
Self::pop_blend(canvas, has_layers);
canvas.pop_layer();
}
}
EffectNodeKind::ColorMatrix { matrix, alpha_mode, source } => {
let matrix = self.matrix(matrix);
if before {
Self::push_matrix(canvas, rect, &matrix, *alpha_mode);
self.paint_around_layer(source, rest, canvas, rect, phase);
} else {
self.paint_around_layer(source, rest, canvas, rect, phase);
Self::pop_matrix(canvas, has_layers && !matrix.is_identity());
}
}
EffectNodeKind::ArithmeticWithColor { multiply, source1, source2, offset, image_is_source1, image, color } => {
let image_index = if *image_is_source1 { 0 } else { 1 };
if index != image_index {
return;
}
let matrix = self.arithmetic_matrix(multiply, source1, source2, offset, *image_is_source1, color);
if before {
Self::push_matrix(canvas, rect, &matrix, ColorMatrixAlphaMode::Straight);
self.paint_around_layer(image, rest, canvas, rect, phase);
} else {
self.paint_around_layer(image, rest, canvas, rect, phase);
Self::pop_matrix(canvas, has_layers && !matrix.is_identity());
}
}
EffectNodeKind::AlphaMask { source, mask } => {
if index == 0 {
if before {
canvas.push_layer(rect);
self.paint_around_layer(source, rest, canvas, rect, phase);
} else {
self.paint_around_layer(source, rest, canvas, rect, phase);
let pushed = Self::push_blend(canvas, rect, BitmapBlendingMode::DestinationIn);
if pushed {
self.paint_node(mask, canvas, rect);
}
Self::pop_blend(canvas, pushed);
canvas.pop_layer();
}
} else if before {
canvas.push_layer(rect);
self.paint_node(source, canvas, rect);
Self::push_blend(canvas, rect, BitmapBlendingMode::DestinationIn);
self.paint_around_layer(mask, rest, canvas, rect, phase);
} else {
self.paint_around_layer(mask, rest, canvas, rect, phase);
Self::pop_blend(canvas, has_layers);
canvas.pop_layer();
}
}
EffectNodeKind::Border { source, .. } => {
self.paint_around_layer(source, rest, canvas, rect, phase);
}
EffectNodeKind::Shadow { amount, color, source } => {
let radius = self.blur_radius(amount);
let matrix = shadow_matrix(self.color(color));
if before {
Self::push_blur(canvas, rect, radius);
Self::push_matrix(canvas, rect, &matrix, ColorMatrixAlphaMode::Premultiplied);
self.paint_around_layer(source, rest, canvas, rect, phase);
} else {
self.paint_around_layer(source, rest, canvas, rect, phase);
Self::pop_matrix(canvas, has_layers && !matrix.is_identity());
let pushed = Self::blur_is_pushed(canvas, radius);
Self::pop_blur(canvas, pushed);
}
}
EffectNodeKind::Pixels { sources, .. } => {
if let Some(layer) = sources.get(index) {
self.paint_around_layer(layer, rest, canvas, rect, phase);
}
}
}
}
fn wrap_layer_in_opacity(
&self,
source: &EffectNode,
rest: &[usize],
canvas: &mut dyn IDrawingContextImpl,
rect: Rect,
phase: LayerPhase,
opacity: f64,
) {
match phase {
LayerPhase::Before => {
if opacity < 1.0 {
canvas.push_opacity(opacity.max(0.0), Some(rect));
}
self.paint_around_layer(source, rest, canvas, rect, phase);
}
LayerPhase::After => {
self.paint_around_layer(source, rest, canvas, rect, phase);
if opacity < 1.0 {
canvas.pop_opacity();
}
}
}
}
pub fn push_for_layer(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
let opacity = self.props.opacity();
if opacity != 1.0 {
canvas.push_opacity(opacity.max(0.0), Some(rect));
}
match (self.graph.root.as_ref(), self.graph.layer_path.as_deref()) {
(Some(root), Some(path)) => self.paint_around_layer(root, path, canvas, rect, LayerPhase::Before),
(root, _) => {
if let Some(root) = root {
self.paint_node(root, canvas, rect);
}
canvas.push_clip(Rect::new(rect.x, rect.y, 0.0, 0.0));
}
}
}
pub fn pop_for_layer(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
match (self.graph.root.as_ref(), self.graph.layer_path.as_deref()) {
(Some(root), Some(path)) => self.paint_around_layer(root, path, canvas, rect, LayerPhase::After),
_ => canvas.pop_clip(),
}
if self.props.opacity() != 1.0 {
canvas.pop_opacity();
}
}
pub fn layer_padding(&self) -> Thickness {
let mut padding = Thickness::default();
let (Some(mut node), Some(path)) = (self.graph.root.as_ref(), self.graph.layer_path.as_deref()) else {
return padding;
};
for &index in path {
if let EffectNodeKind::GaussianBlur { amount, .. } = &node.kind {
let radius = self.blur_radius(amount);
if radius > 0.0 {
let blur = ImmutableBlurEffect::new(radius);
padding = padding + EffectExtensions::get_effect_output_padding(Some(&blur as &dyn IEffect));
}
}
match node.sources().get(index).copied() {
Some(source) => node = source,
None => break,
}
}
padding
}
}
impl_server_render_resource!(
ServerCompositionEffectBrush,
base,
composition_property: ServerCompositionBrushProps::get_composition_property
);
impl ServerCompositionBrushHooks for ServerCompositionEffectBrush {}
impl IServerObject for ServerCompositionEffectBrush {
fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
self.props.deserialize_changes_core(self, reader, committed_at);
let Some(changes) = reader.read_value::<EffectBrushChanges>() else { return };
let compositor = self.base.compositor();
let resolve = |id: ServerObjectId| compositor.as_ref().and_then(|compositor| compositor.get_object(id));
if let Some(id) = changes.properties {
let properties = resolve(id).and_then(|set| set.into_any_rc().downcast::<ServerCompositionPropertySet>().ok());
self.set_properties(properties);
}
if let Some(sources) = changes.sources {
self.set_sources(sources.into_iter().map(|(name, id)| (name, id.and_then(resolve))).collect());
}
}
fn values_invalidated(&self) {
self.base.core().invalidated(self);
}
fn dispose(&self) {
self.set_properties(None);
self.set_sources(Vec::new());
self.base.core().dispose();
}
fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
Some(self)
}
fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
Some(self)
}
fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
self.props.find_props(type_id)
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
pub struct ServerCompositionBackdropBrush {
base: ServerRenderResource,
props: ServerCompositionBrushProps,
}
impl ServerCompositionBackdropBrush {
pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerCompositionBackdropBrush> {
let brush = Rc::new_cyclic(|this: &Weak<ServerCompositionBackdropBrush>| ServerCompositionBackdropBrush {
base: ServerRenderResource::new(
compositor,
this.clone() as Weak<dyn IAnimatedServerObject>,
this.clone() as Weak<dyn IServerRenderResource>,
),
props: ServerCompositionBrushProps::new(),
});
brush.base.object().activate();
brush
}
pub fn brush_props(&self) -> &ServerCompositionBrushProps {
&self.props
}
pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
let opacity = self.props.opacity();
if opacity <= 0.0 {
return;
}
if opacity < 1.0 {
canvas.push_opacity(opacity, Some(rect));
}
crate::rendering::composition::server::draw_backdrop(canvas, rect);
if opacity < 1.0 {
canvas.pop_opacity();
}
}
}
impl_server_render_resource!(
ServerCompositionBackdropBrush,
base,
composition_property: ServerCompositionBrushProps::get_composition_property
);
impl ServerCompositionBrushHooks for ServerCompositionBackdropBrush {}
impl IServerObject for ServerCompositionBackdropBrush {
fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
self.props.deserialize_changes_core(self, reader, committed_at);
}
fn values_invalidated(&self) {
self.base.core().invalidated(self);
}
fn dispose(&self) {
self.base.core().dispose();
}
fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
Some(self)
}
fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
Some(self)
}
fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
self.props.find_props(type_id)
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
pub struct ServerCompositionHostBackdropBrush {
base: ServerRenderResource,
props: ServerCompositionBrushProps,
}
impl ServerCompositionHostBackdropBrush {
pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerCompositionHostBackdropBrush> {
let brush = Rc::new_cyclic(|this: &Weak<ServerCompositionHostBackdropBrush>| ServerCompositionHostBackdropBrush {
base: ServerRenderResource::new(
compositor,
this.clone() as Weak<dyn IAnimatedServerObject>,
this.clone() as Weak<dyn IServerRenderResource>,
),
props: ServerCompositionBrushProps::new(),
});
brush.base.object().activate();
brush
}
pub fn brush_props(&self) -> &ServerCompositionBrushProps {
&self.props
}
pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
let transparent = crate::rendering::composition::server::painting_visual()
.and_then(|visual| visual.root())
.is_some_and(|target| target.transparency_level() != crate::rendering::composition::CompositionTransparencyLevel::None);
if !transparent || self.props.opacity() <= 0.0 {
return;
}
canvas.push_clip(rect);
if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
layers.push_blend_layer(rect, BitmapBlendingMode::Source);
layers.pop_blend_layer();
}
canvas.pop_clip();
}
}
impl_server_render_resource!(
ServerCompositionHostBackdropBrush,
base,
composition_property: ServerCompositionBrushProps::get_composition_property
);
impl ServerCompositionBrushHooks for ServerCompositionHostBackdropBrush {}
impl IServerObject for ServerCompositionHostBackdropBrush {
fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
self.props.deserialize_changes_core(self, reader, committed_at);
}
fn values_invalidated(&self) {
self.base.core().invalidated(self);
}
fn dispose(&self) {
self.base.core().dispose();
}
fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
Some(self)
}
fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
Some(self)
}
fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
self.props.find_props(type_id)
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
fn shadow_matrix(color: Color) -> ColorMatrix {
let channel = |value: u8| f32::from(value) / 255.0;
ColorMatrix::new([
[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, channel(color.a)],
[channel(color.r), channel(color.g), channel(color.b), 0.0],
])
}
fn unpremultiply(pixel: &[u8]) -> ([f32; 3], f32) {
let alpha = f32::from(pixel[3]) / 255.0;
if alpha <= 0.0 {
return ([0.0; 3], 0.0);
}
let channel = |value: u8| (f32::from(value) / 255.0 / alpha).min(1.0);
([channel(pixel[0]), channel(pixel[1]), channel(pixel[2])], alpha)
}
fn store(pixel: &mut [u8], color: [f32; 3], alpha: f32) {
let alpha = alpha.clamp(0.0, 1.0);
for channel in 0..3 {
pixel[channel] = (color[channel].clamp(0.0, 1.0) * alpha * 255.0).round() as u8;
}
pixel[3] = (alpha * 255.0).round() as u8;
}
fn luminosity(color: [f32; 3]) -> f32 {
0.3 * color[0] + 0.59 * color[1] + 0.11 * color[2]
}
fn color_dodge(b: f32, s: f32) -> f32 {
if b <= 0.0 {
0.0
} else if s >= 1.0 {
1.0
} else {
(b / (1.0 - s)).min(1.0)
}
}
fn color_burn(b: f32, s: f32) -> f32 {
if b >= 1.0 {
1.0
} else if s <= 0.0 {
0.0
} else {
1.0 - ((1.0 - b) / s).min(1.0)
}
}
fn dissolve_noise(index: usize) -> f32 {
let mut x = index as u32 ^ 0x9E37_79B9;
x = x.wrapping_mul(0x85EB_CA6B);
x ^= x >> 13;
x = x.wrapping_mul(0xC2B2_AE35);
x ^= x >> 16;
(x & 0xFFFF) as f32 / 65535.0
}
fn blend_pixels(mode: BlendEffectMode, background: &mut [u8], foreground: &[u8], width: usize) {
let _ = width;
for (index, (back, fore)) in background.chunks_exact_mut(4).zip(foreground.chunks_exact(4)).enumerate() {
let (cb, ab) = unpremultiply(back);
let (cs, as_) = unpremultiply(fore);
if mode == BlendEffectMode::Dissolve {
if as_ > 0.0 && dissolve_noise(index) < as_ {
store(back, cs, 1.0);
}
continue;
}
let blended: [f32; 3] = match mode {
BlendEffectMode::DarkerColor => {
if luminosity(cs) < luminosity(cb) {
cs
} else {
cb
}
}
BlendEffectMode::LighterColor => {
if luminosity(cs) > luminosity(cb) {
cs
} else {
cb
}
}
_ => std::array::from_fn(|channel| {
let (b, s) = (cb[channel], cs[channel]);
match mode {
BlendEffectMode::LinearBurn => (b + s - 1.0).max(0.0),
BlendEffectMode::VividLight => {
if s <= 0.5 {
color_burn(b, 2.0 * s)
} else {
color_dodge(b, 2.0 * (s - 0.5))
}
}
BlendEffectMode::LinearLight => (b + 2.0 * s - 1.0).clamp(0.0, 1.0),
BlendEffectMode::PinLight => {
if s <= 0.5 {
b.min(2.0 * s)
} else {
b.max(2.0 * s - 1.0)
}
}
BlendEffectMode::HardMix => {
if b + s >= 1.0 {
1.0
} else {
0.0
}
}
BlendEffectMode::Subtract => (b - s).max(0.0),
BlendEffectMode::Division => {
if s <= 0.0 {
if b <= 0.0 {
0.0
} else {
1.0
}
} else {
(b / s).min(1.0)
}
}
_ => s,
}
}),
};
let alpha = as_ + ab - as_ * ab;
if alpha <= 0.0 {
store(back, [0.0; 3], 0.0);
continue;
}
let premultiplied: [f32; 3] = std::array::from_fn(|channel| {
(1.0 - as_) * ab * cb[channel] + (1.0 - ab) * as_ * cs[channel] + as_ * ab * blended[channel]
});
store(back, premultiplied.map(|value| value / alpha), alpha);
}
}
fn arithmetic_pixels(k: [f32; 4], first: &mut [u8], second: &[u8]) {
for (one, two) in first.chunks_exact_mut(4).zip(second.chunks_exact(4)) {
let mut out = [0.0f32; 4];
for channel in 0..4 {
let (a, b) = (f32::from(one[channel]) / 255.0, f32::from(two[channel]) / 255.0);
out[channel] = (k[0] * a * b + k[1] * a + k[2] * b + k[3]).clamp(0.0, 1.0);
}
for channel in 0..3 {
out[channel] = out[channel].min(out[3]);
}
for channel in 0..4 {
one[channel] = (out[channel] * 255.0).round() as u8;
}
}
}
fn mask_invert_pixels(destination: &mut [u8], source: &[u8]) {
for (dest, src) in destination.chunks_exact_mut(4).zip(source.chunks_exact(4)) {
let source_alpha = f32::from(src[3]) / 255.0;
let dest_alpha = f32::from(dest[3]) / 255.0;
for channel in 0..3 {
let d = f32::from(dest[channel]) / 255.0;
let out = source_alpha * (dest_alpha - d) + (1.0 - source_alpha) * d;
dest[channel] = (out.clamp(0.0, dest_alpha) * 255.0).round() as u8;
}
}
}
#[cfg(test)]
mod pixel_tests {
use super::*;
#[test]
fn the_extra_blend_modes_follow_their_formulas() {
let opaque = |r: u8, g: u8, b: u8| vec![r, g, b, 255];
let mut back = opaque(200, 100, 50);
blend_pixels(BlendEffectMode::Subtract, &mut back, &opaque(100, 150, 25), 1);
assert_eq!(opaque(100, 0, 25), back);
let mut back = opaque(200, 100, 50);
blend_pixels(BlendEffectMode::LinearBurn, &mut back, &opaque(100, 255, 0), 1);
assert_eq!(opaque(45, 100, 0), back);
let mut back = opaque(100, 100, 100);
blend_pixels(BlendEffectMode::HardMix, &mut back, &opaque(200, 100, 0), 1);
assert_eq!(opaque(255, 0, 0), back);
let mut back = opaque(10, 10, 10);
blend_pixels(BlendEffectMode::LighterColor, &mut back, &opaque(200, 0, 0), 1);
assert_eq!(opaque(200, 0, 0), back);
}
#[test]
fn a_blend_over_transparency_is_the_foreground() {
let mut back = vec![0, 0, 0, 0];
blend_pixels(BlendEffectMode::Division, &mut back, &[100, 50, 0, 255], 1);
assert_eq!(vec![100, 50, 0, 255], back);
}
#[test]
fn the_arithmetic_composite_of_two_images() {
let mut first = vec![255, 0, 128, 255];
arithmetic_pixels([0.0, 0.5, 0.5, 0.0], &mut first, &[0, 255, 128, 255]);
assert_eq!(vec![128, 128, 128, 255], first);
}
#[test]
fn mask_invert_inverts_the_destination_under_the_source() {
let mut destination = vec![255, 0, 64, 255, 255, 0, 64, 255];
mask_invert_pixels(&mut destination, &[0, 0, 0, 255, 0, 0, 0, 0]);
assert_eq!(vec![0, 255, 191, 255, 255, 0, 64, 255], destination);
}
}