Skip to main content

wisp_storybook/stories/
s_webcam_shapes.rs

1//! Story: webcam shape masks (M-MASK.8 / AUT-30).
2//!
3//! Two webcam-style overlays side-by-side: circle (creator-style) and
4//! rounded rectangle (professional walkthrough). Both clip a fake
5//! "webcam frame" sprite via `apply_clip`, then composite over a
6//! gradient backdrop so the masked silhouette is visible.
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: "webcam-shapes",
19        category: "Mask",
20        title: "Webcam shapes",
21        milestone: "M-MASK.8 / AUT-30",
22        writeup: include_str!("writeups/webcam_shapes.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    // Backdrop — a gradient panel so the clipped silhouettes pop.
33    let mut bg = Graphics::new();
34    bg.fill(Fill::LinearGradient {
35        start: Vec2::new(0.0, 1.0),
36        end: Vec2::new(0.0, -1.0),
37        color_a: Color::rgba_u8(40, 50, 70, 255),
38        color_b: Color::rgba_u8(20, 25, 35, 255),
39    });
40    bg.draw_rect(Rect::new(-1.0, -1.0, 2.0, 2.0));
41    let _ = stage.add_child(stage.root(), bg);
42
43    // The "webcam frame" — a face-tinted gradient inside a checker
44    // grid so the clip shape is unambiguous.
45    let webcam_frame_rt = render_webcam_frame(&renderer, app, format);
46
47    // Left: circle crop.
48    let circle_clipped = RenderTexture::with_format(app, 256, 256, format);
49    let circle = MaskShape::circle(Vec2::ZERO, 0.85);
50    renderer.apply_clip(app, circle, &webcam_frame_rt, &circle_clipped);
51    let circle_bytes = circle_clipped.read_pixels(app);
52    let circle_tex = Texture::from_rgba(app, 256, 256, &circle_bytes);
53    let mut circle_sprite = Sprite::from_texture(circle_tex).with_anchor(Vec2::splat(0.5));
54    circle_sprite.container.transform.position = Vec2::new(-0.45, 0.0);
55    circle_sprite.container.transform.scale = Vec2::splat(0.4);
56    let _ = stage.add_child(stage.root(), circle_sprite);
57
58    // Right: rounded-rectangle crop.
59    let rounded_clipped = RenderTexture::with_format(app, 256, 256, format);
60    let rounded = MaskShape::rounded_rect(Rect::new(-0.85, -0.85, 1.7, 1.7), 0.18);
61    renderer.apply_clip(app, rounded, &webcam_frame_rt, &rounded_clipped);
62    let rounded_bytes = rounded_clipped.read_pixels(app);
63    let rounded_tex = Texture::from_rgba(app, 256, 256, &rounded_bytes);
64    let mut rounded_sprite = Sprite::from_texture(rounded_tex).with_anchor(Vec2::splat(0.5));
65    rounded_sprite.container.transform.position = Vec2::new(0.45, 0.0);
66    rounded_sprite.container.transform.scale = Vec2::splat(0.4);
67    let _ = stage.add_child(stage.root(), rounded_sprite);
68
69    // Captions under each shape.
70    let mut left_lbl = Graphics::new();
71    left_lbl.fill(Fill::Solid(Color::rgba_u8(140, 200, 240, 220)));
72    left_lbl.draw_rounded_rect(Rect::new(-0.7, -0.55, 0.5, 0.06), 0.014);
73    let mut right_lbl = Graphics::new();
74    right_lbl.fill(Fill::Solid(Color::rgba_u8(220, 220, 130, 220)));
75    right_lbl.draw_rounded_rect(Rect::new(0.2, -0.55, 0.5, 0.06), 0.014);
76    let _ = stage.add_child(stage.root(), left_lbl);
77    let _ = stage.add_child(stage.root(), right_lbl);
78}
79
80fn render_webcam_frame(
81    renderer: &Renderer,
82    app: &Application,
83    format: wgpu::TextureFormat,
84) -> RenderTexture {
85    let mut s = Stage::new();
86    let mut tone = Graphics::new();
87    tone.fill(Fill::LinearGradient {
88        start: Vec2::new(-1.0, 1.0),
89        end: Vec2::new(1.0, -1.0),
90        color_a: Color::rgba_u8(255, 195, 150, 255),
91        color_b: Color::rgba_u8(150, 100, 80, 255),
92    });
93    tone.draw_rect(Rect::new(-1.0, -1.0, 2.0, 2.0));
94    let _ = s.add_child(s.root(), tone);
95    // Checker-ish grid for clip-shape visibility.
96    for i in -3i16..=3 {
97        let x = f32::from(i) * 0.25;
98        let mut line = Graphics::new();
99        line.fill(Fill::Solid(Color::rgba_u8(255, 255, 255, 180)));
100        line.draw_rect(Rect::new(x - 0.01, -1.0, 0.02, 2.0));
101        let _ = s.add_child(s.root(), line);
102    }
103    for j in -3i16..=3 {
104        let y = f32::from(j) * 0.25;
105        let mut line = Graphics::new();
106        line.fill(Fill::Solid(Color::rgba_u8(255, 255, 255, 180)));
107        line.draw_rect(Rect::new(-1.0, y - 0.01, 2.0, 0.02));
108        let _ = s.add_child(s.root(), line);
109    }
110    let rt = RenderTexture::with_format(app, 256, 256, format);
111    let _ = renderer.render_stage(app, rt.view(), Color::TRANSPARENT, &s);
112    rt
113}