Skip to main content

wisp_storybook/stories/
s_privacy_blur_strength.rs

1//! Story: privacy blur strength variants (M-MASK.4 / AUT-22).
2//!
3//! Side-by-side: same shape, three named strengths (Soft / Medium /
4//! Strong). Demonstrates that the editor's strength slider picks
5//! between cinematic preset levels rather than exposing raw pixel
6//! radii.
7
8use glam::Vec2;
9use wisp::application::Application;
10use wisp::math::Rect;
11use wisp::render::Renderer;
12use wisp::{
13    BlurStrength, Color, Fill, Graphics, PrivacyBlur, RenderTexture, Sprite, Stage, Texture,
14};
15
16use crate::story::Story;
17
18pub fn story() -> Story {
19    Story {
20        id: "privacy-blur-strength",
21        category: "Mask",
22        title: "Privacy blur strengths",
23        milestone: "M-MASK.4 / AUT-22",
24        writeup: include_str!("writeups/privacy_blur_strength.md"),
25        build,
26        tick: None,
27    }
28}
29
30fn build(app: &Application, stage: &mut Stage) {
31    let format = wgpu::TextureFormat::Rgba8Unorm;
32    let renderer = Renderer::new(app, format).expect("renderer");
33
34    // One reusable base — colorful gradient + grid lines.
35    let base_rt = render_capture(&renderer, app, format);
36
37    // Three variants, three sprites along the X axis.
38    let strengths = [
39        (BlurStrength::Soft, Vec2::new(-0.62, 0.0)),
40        (BlurStrength::Medium, Vec2::new(0.0, 0.0)),
41        (BlurStrength::Strong, Vec2::new(0.62, 0.0)),
42    ];
43
44    // The privacy region is full-canvas — every shown sprite is
45    // entirely covered by the blur, so the strength comparison is
46    // visible across the whole sprite. Outline drawn separately.
47    let region = Rect::new(-1.0, -1.0, 2.0, 2.0);
48
49    for (strength, pos) in strengths {
50        let blur = PrivacyBlur::rect(region).with_strength(strength);
51        let out_rt = RenderTexture::with_format(app, 192, 192, format);
52        // Re-render the base into an RT sized 192 to match this
53        // sprite's pixel scale; reuse base_rt for now since it's
54        // nominally same content (the renderer scales as the sprite
55        // is laid out).
56        renderer.apply_privacy_blur_data(app, &blur, &base_rt, &out_rt);
57        let bytes = out_rt.read_pixels(app);
58        let tex = Texture::from_rgba(app, 192, 192, &bytes);
59        let mut sprite = Sprite::from_texture(tex).with_anchor(Vec2::splat(0.5));
60        sprite.container.transform.position = pos;
61        sprite.container.transform.scale = Vec2::splat(0.28);
62        let _ = stage.add_child(stage.root(), sprite);
63    }
64
65    // Strength labels (tiny tinted bars beneath each sprite) so the
66    // viewer can tell which is which at a glance.
67    let label_y = -0.32;
68    let label_h = 0.05;
69    let label_w = 0.32;
70    let labels = [
71        (
72            Vec2::new(-0.62, label_y),
73            Color::rgba_u8(140, 200, 240, 230),
74        ), // Soft (cool blue)
75        (Vec2::new(0.0, label_y), Color::rgba_u8(220, 220, 130, 230)), // Medium (yellow)
76        (Vec2::new(0.62, label_y), Color::rgba_u8(240, 130, 130, 230)), // Strong (warm red)
77    ];
78    for (pos, color) in labels {
79        let mut bar = Graphics::new();
80        bar.fill(Fill::Solid(color));
81        bar.draw_rounded_rect(
82            Rect::new(
83                pos.x - label_w * 0.5,
84                pos.y - label_h * 0.5,
85                label_w,
86                label_h,
87            ),
88            0.012,
89        );
90        let _ = stage.add_child(stage.root(), bar);
91    }
92}
93
94fn render_capture(
95    renderer: &Renderer,
96    app: &Application,
97    format: wgpu::TextureFormat,
98) -> RenderTexture {
99    let mut s = Stage::new();
100    let mut bg = Graphics::new();
101    bg.fill(Fill::LinearGradient {
102        start: Vec2::new(-1.0, 1.0),
103        end: Vec2::new(1.0, -1.0),
104        color_a: Color::rgba_u8(80, 130, 210, 255),
105        color_b: Color::rgba_u8(220, 110, 80, 255),
106    });
107    bg.draw_rect(Rect::new(-1.0, -1.0, 2.0, 2.0));
108    let _ = s.add_child(s.root(), bg);
109    for i in -4i16..=4 {
110        let x = f32::from(i) * 0.2;
111        let mut line = Graphics::new();
112        line.fill(Fill::Solid(Color::rgba_u8(255, 255, 255, 200)));
113        line.draw_rect(Rect::new(x - 0.005, -1.0, 0.01, 2.0));
114        let _ = s.add_child(s.root(), line);
115    }
116    for j in -4i16..=4 {
117        let y = f32::from(j) * 0.2;
118        let mut line = Graphics::new();
119        line.fill(Fill::Solid(Color::rgba_u8(255, 255, 255, 200)));
120        line.draw_rect(Rect::new(-1.0, y - 0.005, 2.0, 0.01));
121        let _ = s.add_child(s.root(), line);
122    }
123    let rt = RenderTexture::with_format(app, 192, 192, format);
124    let _ = renderer.render_stage(app, rt.view(), Color::BLACK, &s);
125    rt
126}