use std::sync::Arc;
use valo_dl::{Image, Paint};
use valo_geometry::{FillRule, Matrix, Path, Rect};
use crate::pipelines::{AdvancedBlend, Blend, PipelineBlend};
use super::draw_state::DrawState;
use super::filter_tree::{DrawMaskBlur, FilterTree};
use super::layers::SourceSpace;
use super::primitives::BlendedSolid;
use super::source::{DrawSource, Shape};
use super::Planner;
pub(super) enum Route<'a> {
Direct(PipelineBlend),
EffectLayer {
space: SourceSpace,
filter: FilterTree<'a>,
mask_blur: Option<DrawMaskBlur<'a>>,
blend: Blend,
},
FilteredImage {
space: SourceSpace,
filter: FilterTree<'a>,
image: &'a Image,
dst: Rect,
blend: Blend,
},
BlendedRect { rect: Rect, mode: AdvancedBlend },
BlendedPath {
path: &'a Arc<Path>,
rule: FillRule,
mode: AdvancedBlend,
},
InImplicitLayer(Blend),
Nothing,
}
impl<'a> Route<'a> {
pub fn of(source: &DrawSource<'a>, paint: &Paint, transform: &Matrix) -> Self {
let blend = Blend::of(paint.blend_mode);
if let DrawSource::RRectBlur { .. } = source {
return Route::blended_whole(blend);
}
if needs_effect_layer(source, paint) {
let Some(space) = SourceSpace::of(transform) else {
return Route::Nothing;
};
if let Some(route) = Route::through_filters(source, paint, space, blend) {
return route;
}
}
match (blend, *source) {
(Blend::ReadsDestination(mode), source) => Route::blended(&source, paint, mode),
(Blend::Fixed(_), DrawSource::Glyphs(_)) if paint.shader.is_some() => {
Route::InImplicitLayer(blend)
}
(Blend::Fixed(blend), _) => Route::Direct(blend),
}
}
fn through_filters(
source: &DrawSource<'a>,
paint: &Paint,
space: SourceSpace,
blend: Blend,
) -> Option<Self> {
let mask_blur = paint
.mask_blur
.and_then(|blur| source.mask_blur(paint, blur));
let filter = FilterTree::for_draw(
paint,
mask_blur.as_ref(),
space.source,
source.blur_tile_mode(),
)?;
let whole_image = source
.whole_image()
.filter(|_| mask_blur.is_none() && !paint.draw_fills_clip());
Some(match whole_image {
Some((image, dst)) => Route::FilteredImage {
space,
filter,
image,
dst,
blend,
},
None => Route::EffectLayer {
space,
filter,
mask_blur,
blend,
},
})
}
fn blended(source: &DrawSource<'a>, paint: &Paint, mode: AdvancedBlend) -> Self {
let in_layer = Route::InImplicitLayer(Blend::ReadsDestination(mode));
match *source {
DrawSource::Shape { shape, .. } if paint.shader.is_none() => match shape {
Shape::Rect(rect) => Route::BlendedRect { rect, mode },
Shape::Path { path, rule } => Route::BlendedPath { path, rule, mode },
Shape::Stroke { .. } => in_layer,
},
_ => in_layer,
}
}
fn blended_whole(blend: Blend) -> Self {
match blend {
Blend::Fixed(blend) => Route::Direct(blend),
Blend::ReadsDestination(_) => Route::InImplicitLayer(blend),
}
}
}
impl Planner<'_> {
pub(super) fn plan_routed(
&mut self,
mut source: DrawSource<'_>,
paint: &Paint,
at: &DrawState,
) {
let prepared = self.prepare_paint(&mut source, paint);
let paint = prepared.as_ref().unwrap_or(paint);
let shown = match Route::of(&source, paint, &at.transform) {
Route::Direct(blend) => {
self.drawing().draw(source, paint, at, blend);
Some(())
}
Route::EffectLayer {
space,
filter,
mask_blur,
blend,
} => {
let layer = EffectLayer {
space: &space,
filter: &filter,
mask_blur: mask_blur.as_ref(),
blend,
};
self.plan_effect_layer(source, paint, at, layer)
}
Route::FilteredImage {
space,
filter,
image,
dst,
blend,
} => self.plan_filtered_image(image, &dst, paint, &filter, &space, at, blend),
Route::BlendedRect { rect, mode } => {
self.plan_blended_solid(BlendedSolid::Rect(rect), paint, at, mode);
Some(())
}
Route::BlendedPath { path, rule, mode } => {
self.plan_blended_path(path, rule, paint, at, mode)
}
Route::InImplicitLayer(blend) => self.plan_in_implicit_layer(source, paint, at, blend),
Route::Nothing => None,
};
match shown {
Some(()) => self.plan.stats.draws += 1,
None => self.plan.stats.culled += 1,
}
}
fn prepare_paint(&mut self, source: &mut DrawSource<'_>, paint: &Paint) -> Option<Paint> {
match source {
DrawSource::Image { .. } => None,
DrawSource::Glyphs(run) if paint.shader.is_none() => {
run.colour_filter = paint.color_filter;
run.colour_filter.map(|_| Paint {
color_filter: None,
..paint.clone()
})
}
_ => folded_paint(paint).or_else(|| self.bake_pattern_colour_filter(paint)),
}
}
fn plan_effect_layer(
&mut self,
source: DrawSource<'_>,
paint: &Paint,
at: &DrawState,
layer: EffectLayer<'_, '_>,
) -> Option<()> {
let content_bounds = (!paint.draw_fills_clip()).then(|| source.local_bounds());
let (content, content_paint) = source.in_effect_layer(paint, layer.mask_blur);
let picture = self.render_effect_layer(
content,
&content_paint,
content_bounds.as_ref(),
layer.filter,
layer.space,
)?;
let composite = at.with_transform(layer.space.remainder);
self.composite_picture(picture, Some(layer.filter), layer.blend, &composite);
Some(())
}
fn plan_blended_solid(
&mut self,
solid: BlendedSolid,
paint: &Paint,
at: &DrawState,
mode: AdvancedBlend,
) {
let destination = self.split_for_copy(at.z);
self.drawing()
.draw_blended_solid(solid, paint, at, mode, &destination.view);
}
fn plan_blended_path(
&mut self,
path: &Arc<Path>,
rule: FillRule,
paint: &Paint,
at: &DrawState,
mode: AdvancedBlend,
) -> Option<()> {
let fan = self.drawing().fan_mesh(path, &at.transform)?;
let solid = BlendedSolid::Path {
bounds: path.bounds(),
fan,
rule,
};
self.plan_blended_solid(solid, paint, at, mode);
Some(())
}
fn plan_in_implicit_layer(
&mut self,
source: DrawSource<'_>,
paint: &Paint,
at: &DrawState,
blend: Blend,
) -> Option<()> {
let device_bounds = source.device_bounds(&at.transform);
self.plan_via_implicit_layer(device_bounds, at, blend, |drawing, inner| {
drawing.draw_alone(source, paint, inner);
})
}
}
struct EffectLayer<'r, 'a> {
space: &'r SourceSpace,
filter: &'r FilterTree<'a>,
mask_blur: Option<&'r DrawMaskBlur<'a>>,
blend: Blend,
}
fn needs_effect_layer(source: &DrawSource<'_>, paint: &Paint) -> bool {
let blur_family = paint.mask_blur.is_some() || paint.effective_image_filter().is_some();
match source {
DrawSource::Image { .. } => blur_family,
_ => blur_family || paint.color_filter.is_some(),
}
}
fn folded_paint(paint: &Paint) -> Option<Paint> {
let filter = paint.color_filter?;
let mut folded = paint.clone();
match &mut folded.shader {
Some(shader) => {
if !shader.fold_color_filter(&filter) {
return None;
}
}
None => folded.color = filter.folded_into(paint.color),
}
folded.color_filter = None;
Some(folded)
}
#[cfg(test)]
mod tests {
use super::*;
use valo_dl::{BlendMode, ImageFilter, MaskBlur, Shader};
use valo_geometry::{Color, PathBuilder, Point, Stroke};
const RECT: Rect = Rect {
x: 10.0,
y: 10.0,
width: 20.0,
height: 20.0,
};
fn rect() -> DrawSource<'static> {
DrawSource::Shape {
shape: Shape::Rect(RECT),
ink: RECT,
}
}
fn triangle() -> Arc<Path> {
let mut path = PathBuilder::new();
path.move_to((0.0, 0.0))
.line_to((10.0, 0.0))
.line_to((0.0, 10.0))
.close();
path.build()
}
fn multiply() -> Paint {
Paint {
blend_mode: BlendMode::Multiply,
..Paint::default()
}
}
fn blurred() -> Paint {
Paint {
image_filter: Some(ImageFilter::blur(2.0, 2.0)),
..Paint::default()
}
}
fn route_name(route: Route<'_>) -> &'static str {
match route {
Route::Direct(_) => "direct",
Route::EffectLayer { .. } => "effect layer",
Route::FilteredImage { .. } => "filtered image",
Route::BlendedRect { .. } => "blended rect",
Route::BlendedPath { .. } => "blended path",
Route::InImplicitLayer(_) => "in implicit layer",
Route::Nothing => "nothing",
}
}
fn route(source: &DrawSource<'_>, paint: &Paint) -> &'static str {
route_name(Route::of(source, paint, &Matrix::IDENTITY))
}
#[test]
fn a_plain_draw_is_direct_and_one_with_filters_takes_a_layer() {
assert_eq!(route(&rect(), &Paint::default()), "direct");
assert_eq!(route(&rect(), &blurred()), "effect layer");
let masked = Paint {
mask_blur: Some(MaskBlur::new(3.0)),
..Paint::default()
};
assert_eq!(route(&rect(), &masked), "effect layer");
}
#[test]
fn a_draw_whose_filters_do_nothing_is_direct() {
let nothing = Paint {
image_filter: Some(ImageFilter::blur(0.0, 0.0)),
..Paint::default()
};
assert_eq!(route(&rect(), ¬hing), "direct");
}
#[test]
fn a_filtered_draw_under_a_flat_transform_shows_nothing() {
let flat = Matrix::scale(1.0, 0.0);
assert_eq!(route_name(Route::of(&rect(), &blurred(), &flat)), "nothing");
}
#[test]
fn a_solid_shape_blends_in_its_own_fragment_and_anything_else_in_a_layer() {
let path = triangle();
let filled = DrawSource::Shape {
shape: Shape::Path {
path: &path,
rule: FillRule::NonZero,
},
ink: path.bounds(),
};
let stroke = Stroke::new(2.0);
let stroked = DrawSource::Shape {
shape: Shape::Stroke {
path: &path,
stroke: &stroke,
},
ink: path.bounds(),
};
let gradient = Paint {
shader: Some(Shader::linear(
Point::new(0.0, 0.0),
Point::new(10.0, 0.0),
Color::BLACK,
Color::WHITE,
)),
..multiply()
};
assert_eq!(route(&rect(), &multiply()), "blended rect");
assert_eq!(route(&filled, &multiply()), "blended path");
assert_eq!(route(&stroked, &multiply()), "in implicit layer");
assert_eq!(route(&rect(), &gradient), "in implicit layer");
let rrect_blur = DrawSource::RRectBlur {
rect: RECT,
radii: [2.0; 4],
blur: MaskBlur::new(2.0),
};
assert_eq!(route(&rrect_blur, &multiply()), "in implicit layer");
assert_eq!(route(&rrect_blur, &Paint::default()), "direct");
}
#[test]
fn a_draw_with_filters_and_a_destination_reading_blend_takes_its_effect_layer() {
let paint = Paint {
blend_mode: BlendMode::Multiply,
..blurred()
};
assert_eq!(route(&rect(), &paint), "effect layer");
}
}