use valo_dl::{BlendMode, BlurStyle, ClipOp, ColorFilter, Image, Paint, TileMode};
use crate::pipelines::{Blend, PipelineBlend};
use valo_geometry::{Color, Matrix, Rect};
use super::draw_state::DrawState;
use super::filter_output::{FilterOutput, Merge, StyleClip};
use super::filter_passes::FilterPasses;
use super::filter_tree::{device_offset, Fill, Filter, FilterInput, FilterTree, StyleShape};
use super::gaussian::{BlurBounds, BlurPlan};
use super::layers::SourceSpace;
use super::snapshot::{Placement, Snapshot};
use super::source::Shape;
use super::Planner;
impl Planner<'_> {
pub(super) fn composite_picture(
&mut self,
picture: Snapshot,
filter: Option<&FilterTree<'_>>,
blend: Blend,
at: &DrawState,
) {
let output = match filter {
None => FilterOutput::Snapshot(picture),
Some(filter) => match self.render_filters(filter, &picture, &at.transform) {
Some(output) => output,
None => return,
},
};
self.composite_output(output, blend, at);
}
#[expect(
clippy::too_many_arguments,
reason = "the image's draw, its route's decisions and the state"
)]
pub(super) fn plan_filtered_image(
&mut self,
image: &Image,
dst: &Rect,
paint: &Paint,
filter: &FilterTree<'_>,
space: &SourceSpace,
at: &DrawState,
blend: Blend,
) -> Option<()> {
let texture = Snapshot::of_image(image, dst, &space.source);
let output = self.render_filters(filter, &texture, &space.remainder)?;
let composite = at.with_transform(space.remainder).faded(paint.color.a);
self.composite_output(output, blend, &composite);
Some(())
}
fn render_filters<'t>(
&mut self,
filter: &FilterTree<'t>,
picture: &Snapshot,
placement: &Matrix,
) -> Option<FilterOutput<'t>> {
let area = self.contexts.top().area;
let coverage = placement.map_rect(&filter.coverage(&picture.placement.coverage()));
if area.culls(&coverage) {
return None;
}
let hint = picture.placement.placed.then(|| area.rect());
Some(self.render_filter_tree(&filter.root, picture, &filter.effect_transform, hint))
}
pub(super) fn render_filter_tree<'t>(
&mut self,
tree: &FilterInput<'t>,
source: &Snapshot,
effect_transform: &Matrix,
hint: Option<Rect>,
) -> FilterOutput<'t> {
match tree {
FilterInput::Source => FilterOutput::Snapshot(source.clone()),
FilterInput::FillIn { mask, fill } => {
let mask = self.render_filter_tree(mask, source, effect_transform, None);
self.render_fill_in(&mask, fill, effect_transform, hint)
}
FilterInput::Filter(node) => {
let input_hint = node.filter.input_hint(hint, effect_transform);
let input =
self.render_filter_tree(&node.input, source, effect_transform, input_hint);
let input = self.snapshot_output(input);
self.render_filter(&node.filter, input, effect_transform, input_hint)
}
}
}
fn render_filter<'t>(
&mut self,
filter: &Filter<'t>,
input: Snapshot,
effect_transform: &Matrix,
input_hint: Option<Rect>,
) -> FilterOutput<'t> {
let plan = |placement: &Placement, tile_mode, bounds| {
let blur = filter.blur(effect_transform)?;
BlurPlan::new(&blur, placement, input_hint.as_ref(), tile_mode, bounds)
};
match filter {
Filter::Blur {
bounds, tile_mode, ..
} => {
let bounds = bounds.as_ref().map(|rect| BlurBounds {
rect,
local_to_tree: effect_transform,
});
let blurred = match plan(&input.placement, *tile_mode, bounds) {
Some(plan) => self.filter_passes().push_blur(&plan, &input.view),
None => input,
};
FilterOutput::Snapshot(blurred)
}
Filter::MaskBlur { blur, style_shape } => {
let Some(plan) = plan(&input.placement, TileMode::Decal, None) else {
return FilterOutput::Snapshot(input);
};
let blurred = self.filter_passes().push_blur(&plan, &input.view);
let style = MaskStyle {
style: blur.style,
shape: *style_shape,
transform: effect_transform,
};
style.apply(blurred, input)
}
Filter::Color(filter) => FilterOutput::Recolour {
input,
filter: *filter,
},
Filter::DropShadow { offset, color, .. } => {
let tinted = self.filter_passes().push_shadow_tint(&input, *color);
let shadow = match plan(&tinted.placement, TileMode::Decal, None) {
Some(plan) => self.filter_passes().push_blur(&plan, &tinted.view),
None => tinted,
};
drop_shadow(input, shadow, device_offset(effect_transform, *offset))
}
}
}
fn render_fill_in(
&mut self,
mask: &FilterOutput<'_>,
fill: &Fill<'_>,
effect_transform: &Matrix,
hint: Option<Rect>,
) -> FilterOutput<'static> {
let transform = *effect_transform;
let coverage = mask
.coverage()
.union(&transform.map_rect(&fill.local_bounds()));
let region = hint
.and_then(|hint| coverage.intersect(&hint))
.unwrap_or(coverage);
let snapshot = self.render_to_snapshot(®ion, |drawing, depths| {
let mask_at = DrawState::in_layer(Matrix::IDENTITY, depths.first);
drawing.draw_filter_output(mask, PipelineBlend::SrcOver, &mask_at);
let (contents, paint) = fill.as_draw();
let fill_at = DrawState::in_layer(transform, depths.second);
drawing.draw(contents, paint, &fill_at, PipelineBlend::SrcIn);
});
FilterOutput::Snapshot(snapshot)
}
}
impl FilterPasses<'_, '_> {
fn push_shadow_tint(&mut self, source: &Snapshot, color: Color) -> Snapshot {
let tint = ColorFilter::Blend(color, BlendMode::SrcIn);
self.push_recolour(source, tint, 1.0)
}
}
fn drop_shadow(source: Snapshot, mut shadow: Snapshot, offset: [f32; 2]) -> FilterOutput<'static> {
let [x, y] = offset;
shadow.placement.transform = Matrix::translation(x, y).then(&shadow.placement.transform);
FilterOutput::Merge {
first: shadow,
second: source,
merge: Merge::DropShadow,
}
}
struct MaskStyle<'m, 't> {
style: BlurStyle,
shape: StyleShape<'t>,
transform: &'m Matrix,
}
impl<'t> MaskStyle<'_, 't> {
fn apply(self, blurred: Snapshot, sharp: Snapshot) -> FilterOutput<'t> {
match (self.style, self.shape) {
(BlurStyle::Normal, _) => FilterOutput::Snapshot(blurred),
(style, StyleShape::GlyphRun) => FilterOutput::Merge {
first: blurred,
second: sharp,
merge: Merge::BlurStyle(style),
},
(BlurStyle::Solid, StyleShape::Shape(_)) => FilterOutput::Solid {
sharp,
blur: blurred,
},
(BlurStyle::Inner, StyleShape::Shape(shape)) => {
self.clipped(blurred, shape, ClipOp::Intersect)
}
(BlurStyle::Outer, StyleShape::Shape(shape)) => {
self.clipped(blurred, shape, ClipOp::Difference)
}
}
}
fn clipped(&self, blurred: Snapshot, shape: Shape<'t>, op: ClipOp) -> FilterOutput<'t> {
FilterOutput::Clipped {
blur: blurred,
clip: StyleClip {
shape,
op,
transform: *self.transform,
},
}
}
}