Skip to main content

wisp_storybook/stories/
s_privacy_blur_rect.rs

1//! Story: rectangle privacy blur (M-MASK.2 / AUT-20).
2//!
3//! Pre-render a "screen capture" full of legible content (gradient
4//! panels + grid lines), then privacy-blur a sub-rectangle so a
5//! sensitive region of the recording is occluded while the rest stays
6//! sharp. Demonstrates `Renderer::apply_privacy_blur` end-to-end.
7
8use glam::Vec2;
9use wisp::application::Application;
10use wisp::math::Rect;
11use wisp::render::Renderer;
12use wisp::{Color, Fill, Graphics, MaskShape, RenderTexture, Sprite, Stage, Texture};
13
14use crate::story::Story;
15
16pub fn story() -> Story {
17    Story {
18        id: "privacy-blur-rect",
19        category: "Mask",
20        title: "Rectangle privacy blur",
21        milestone: "M-MASK.2 / AUT-20",
22        writeup: include_str!("writeups/privacy_blur_rect.md"),
23        build,
24        tick: None,
25    }
26}
27
28fn build(app: &Application, stage: &mut Stage) {
29    let format = wgpu::TextureFormat::Rgba8Unorm;
30    let renderer = Renderer::new(app, format).expect("renderer");
31
32    // Pretend "screen capture" RT — a colorful gradient with a grid
33    // overlay so the blur is visible (sharp lines disappear inside the
34    // privacy region).
35    let mut capture_stage = Stage::new();
36
37    let mut bg = Graphics::new();
38    bg.fill(Fill::LinearGradient {
39        start: Vec2::new(-1.0, 1.0),
40        end: Vec2::new(1.0, -1.0),
41        color_a: Color::rgba_u8(80, 130, 210, 255),
42        color_b: Color::rgba_u8(220, 110, 80, 255),
43    });
44    bg.draw_rect(Rect::new(-1.0, -1.0, 2.0, 2.0));
45    let _ = capture_stage.add_child(capture_stage.root(), bg);
46
47    // Vertical grid lines — these reveal the blur sharply.
48    for i in -4i16..=4 {
49        let x = f32::from(i) * 0.2;
50        let mut line = Graphics::new();
51        line.fill(Fill::Solid(Color::rgba_u8(255, 255, 255, 200)));
52        line.draw_rect(Rect::new(x - 0.005, -1.0, 0.01, 2.0));
53        let _ = capture_stage.add_child(capture_stage.root(), line);
54    }
55    for j in -4i16..=4 {
56        let y = f32::from(j) * 0.2;
57        let mut line = Graphics::new();
58        line.fill(Fill::Solid(Color::rgba_u8(255, 255, 255, 200)));
59        line.draw_rect(Rect::new(-1.0, y - 0.005, 2.0, 0.01));
60        let _ = capture_stage.add_child(capture_stage.root(), line);
61    }
62
63    let base_rt = RenderTexture::with_format(app, 256, 256, format);
64    let _ = renderer.render_stage(app, base_rt.view(), Color::BLACK, &capture_stage);
65
66    // Apply the privacy blur — center-right rect, a chunk of the grid
67    // ends up obscured.
68    let output_rt = RenderTexture::with_format(app, 256, 256, format);
69    let region = Rect::new(0.05, -0.45, 0.55, 0.6);
70    renderer.apply_privacy_blur(app, MaskShape::rect(region), 12.0, &base_rt, &output_rt);
71
72    let bytes = output_rt.read_pixels(app);
73    let result_tex = Texture::from_rgba(app, 256, 256, &bytes);
74
75    let mut shown = Sprite::from_texture(result_tex).with_anchor(Vec2::splat(0.5));
76    shown.container.transform.scale = Vec2::splat(0.9);
77    let _ = stage.add_child(stage.root(), shown);
78
79    // Outline the privacy region so viewers can see what was redacted.
80    let mut outline = Graphics::new();
81    outline.fill(Fill::Solid(Color::rgba_u8(255, 240, 0, 200)));
82    let inner = Rect::new(0.05 * 0.9, -0.45 * 0.9, 0.55 * 0.9, 0.6 * 0.9);
83    let t = 0.008;
84    outline.draw_rect(Rect::new(inner.min.x, inner.min.y, inner.size.x, t));
85    outline.draw_rect(Rect::new(
86        inner.min.x,
87        inner.min.y + inner.size.y - t,
88        inner.size.x,
89        t,
90    ));
91    outline.draw_rect(Rect::new(inner.min.x, inner.min.y, t, inner.size.y));
92    outline.draw_rect(Rect::new(
93        inner.min.x + inner.size.x - t,
94        inner.min.y,
95        t,
96        inner.size.y,
97    ));
98    let _ = stage.add_child(stage.root(), outline);
99}