Skip to main content

cranpose_ui_graphics/
gradient_blur.rs

1//! Spatially varying backdrop blur.
2//!
3//! Unlike an alpha gradient drawn over a uniformly blurred surface, this
4//! shader changes the sampling kernel radius at every fragment. It is intended
5//! for edge-to-edge system-bar treatments where content should move smoothly
6//! from sharp to fully frosted without a visible material boundary.
7
8use crate::{RenderEffect, RuntimeShader, SubstrateSpec};
9
10/// Axis and direction used to interpolate the blur radius.
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
12pub enum GradientBlurDirection {
13    LeftToRight,
14    RightToLeft,
15    #[default]
16    TopToBottom,
17    BottomToTop,
18}
19
20impl GradientBlurDirection {
21    fn uniform_code(self) -> f32 {
22        match self {
23            Self::LeftToRight => 0.0,
24            Self::RightToLeft => 1.0,
25            Self::TopToBottom => 2.0,
26            Self::BottomToTop => 3.0,
27        }
28    }
29}
30
31/// WGSL implementation of a per-fragment Gaussian-style blur kernel.
32pub const GRADIENT_BLUR_WGSL: &str = runtime_shader_wgsl!("gradient_blur.wgsl");
33
34/// The blur levels' shares of the wide radius: the renderer blurs the
35/// capture by each, and the shader blends the sharp source and the levels
36/// around the radius each fragment asks for.
37pub const GRADIENT_BLUR_LEVELS: [f32; 3] = [1.0, 0.5, 0.25];
38
39/// Build a spatially varying blur effect.
40///
41/// Radii are physical pixels. The radius interpolates smoothly across the
42/// complete effect bounds in `direction`; callers normally reach this through
43/// `Modifier::backdrop_gradient_blur`, which converts from dp automatically.
44/// The shader declares three blurred substrates, the wide radius halved
45/// twice, and realises every radius between as a blend of the level below
46/// and the level above it.
47pub fn gradient_blur_effect(
48    start_radius_px: f32,
49    end_radius_px: f32,
50    direction: GradientBlurDirection,
51) -> RenderEffect {
52    let start_radius_px = start_radius_px.max(0.0);
53    let end_radius_px = end_radius_px.max(0.0);
54    let mut shader = RuntimeShader::new(GRADIENT_BLUR_WGSL);
55    shader.set_position_independent(true);
56    shader.set_float(0, start_radius_px);
57    shader.set_float(1, end_radius_px);
58    shader.set_float(2, direction.uniform_code());
59    let wide_radius_px = start_radius_px.max(end_radius_px);
60    shader.set_input_padding(wide_radius_px.ceil());
61    shader.set_batched_source(true);
62    shader.set_substrates(&GRADIENT_BLUR_LEVELS.map(|level| SubstrateSpec::Blur {
63        radius_px: wide_radius_px * level,
64    }));
65    RenderEffect::runtime_shader(shader)
66}
67
68#[cfg(test)]
69#[path = "tests/gradient_blur_tests.rs"]
70mod tests;