1use glam::Vec2;
10use wisp::application::Application;
11use wisp::math::Rect;
12use wisp::render::Renderer;
13use wisp::text::{TextTexturePipeline, WispText, WispTextStyle};
14use wisp::texture::render_texture::RenderTexture;
15use wisp::{
16 BlurFilter, Color, DropShadowFilter, Fill, Graphics, Sprite, Stage, Stroke, Texture,
17 WispFontWeight,
18};
19
20use crate::story::Story;
21
22pub fn story() -> Story {
23 Story {
24 id: "text-mask",
25 category: "Text",
26 title: "Text as mask — fill / blur / spotlight",
27 milestone: "M-TEXT.11",
28 writeup: include_str!("writeups/text_mask.md"),
29 build,
30 tick: None,
31 }
32}
33
34const SIZE: u32 = 256;
35
36fn build(app: &Application, stage: &mut Stage) {
37 let format = wgpu::TextureFormat::Rgba8Unorm;
38 let renderer = Renderer::new(app, format).expect("renderer");
39 let pipeline = TextTexturePipeline::new(app, wgpu::TextureFormat::Rgba8UnormSrgb);
40 let root = stage.root();
41
42 let text = WispText::new("WISP").with_style(
44 WispTextStyle::default()
45 .with_size(0.95)
46 .with_weight(WispFontWeight::Bold)
47 .with_color(Color::WHITE),
48 );
49 let text_rt = pipeline.render(app, &text, SIZE, SIZE);
50 let text_bytes = text_rt.read_pixels(app);
51 let text_tex = Texture::from_rgba(app, SIZE, SIZE, &text_bytes);
52
53 let mask_rt = RenderTexture::with_format(app, SIZE, SIZE, format);
54 let mut mask_stage = Stage::new();
55 let mut mask_sprite = Sprite::from_texture(text_tex.clone()).with_anchor(Vec2::new(0.0, 0.0));
56 mask_sprite.container.transform.position = Vec2::new(-1.0, 1.0);
57 mask_sprite.container.transform.scale = Vec2::new(2.0, -2.0);
58 let _ = mask_stage.add_child(mask_stage.root(), mask_sprite);
59 let _ = renderer.render_stage(app, mask_rt.view(), Color::TRANSPARENT, &mask_stage);
60
61 let fill_rt = render_gradient_fill(app, &renderer, format);
63 let blur_rt = render_blur_backdrop(app, &renderer, format);
64 let spot_rt = render_spotlight(app, &renderer, format);
65
66 let fill_out = RenderTexture::with_format(app, SIZE, SIZE, format);
68 renderer.apply_mask_to_texture(app, &fill_rt, &mask_rt, &fill_out);
69
70 let blur_out = RenderTexture::with_format(app, SIZE, SIZE, format);
71 renderer.apply_mask_to_texture(app, &blur_rt, &mask_rt, &blur_out);
72
73 let spot_out = RenderTexture::with_format(app, SIZE, SIZE, format);
74 renderer.apply_mask_to_texture(app, &spot_rt, &mask_rt, &spot_out);
75
76 let backdrop_rt = RenderTexture::with_format(app, SIZE, SIZE, format);
78 let _ = renderer.render_stage(
79 app,
80 backdrop_rt.view(),
81 Color::rgba(0.94, 0.95, 0.97, 1.0),
82 &Stage::new(),
83 );
84 let backdrop_bytes = backdrop_rt.read_pixels(app);
85 let backdrop_tex = Texture::from_rgba(app, SIZE, SIZE, &backdrop_bytes);
86 let mut backdrop = Sprite::from_texture(backdrop_tex).with_anchor(Vec2::splat(0.5));
87 backdrop.container.transform.scale = Vec2::splat(2.0);
88 let _ = stage.add_child(root, backdrop);
89
90 place_masked(stage, root, &fill_out, app, 0.55);
92 place_masked(stage, root, &blur_out, app, 0.05);
93 place_masked(stage, root, &spot_out, app, -0.45);
94}
95
96fn place_masked(
97 stage: &mut Stage,
98 root: wisp::NodeId,
99 rt: &RenderTexture,
100 app: &Application,
101 y: f32,
102) {
103 let bytes = rt.read_pixels(app);
104 let tex = Texture::from_rgba(app, SIZE, SIZE, &bytes);
105 let mut sprite = Sprite::from_texture(tex).with_anchor(Vec2::splat(0.5));
106 sprite.container.transform.position = Vec2::new(0.0, y);
107 sprite.container.transform.scale = Vec2::new(1.8, -0.4);
108 let _ = stage.add_child(root, sprite);
109}
110
111fn render_gradient_fill(
112 app: &Application,
113 renderer: &Renderer,
114 format: wgpu::TextureFormat,
115) -> RenderTexture {
116 let rt = RenderTexture::with_format(app, SIZE, SIZE, format);
119 let mut s = Stage::new();
120 let bands = [
121 (Color::rgba_u8(255, 100, 80, 255), -1.0),
122 (Color::rgba_u8(255, 200, 60, 255), -0.5),
123 (Color::rgba_u8(80, 220, 120, 255), 0.0),
124 (Color::rgba_u8(60, 170, 240, 255), 0.5),
125 ];
126 let mut g = Graphics::new();
127 for (color, y0) in bands {
128 g.fill(Fill::Solid(color));
129 g.draw_rect(Rect::new(-1.0, y0, 2.0, 0.5));
130 }
131 let _ = s.add_child(s.root(), g);
132 let _ = renderer.render_stage(app, rt.view(), Color::TRANSPARENT, &s);
133 rt
134}
135
136fn render_blur_backdrop(
137 app: &Application,
138 renderer: &Renderer,
139 format: wgpu::TextureFormat,
140) -> RenderTexture {
141 if std::env::var_os("WISP_SKIP_GPU_FILTER_TESTS").is_some() {
146 return render_circles(app, renderer, format);
147 }
148 let sharp = render_circles(app, renderer, format);
149 let blurred = RenderTexture::with_format(app, SIZE, SIZE, format);
150 renderer.apply_filter(app, &BlurFilter { radius: 8.0 }, &sharp, &blurred);
151 blurred
152}
153
154fn render_circles(
155 app: &Application,
156 renderer: &Renderer,
157 format: wgpu::TextureFormat,
158) -> RenderTexture {
159 let rt = RenderTexture::with_format(app, SIZE, SIZE, format);
160 let mut s = Stage::new();
161 let mut g = Graphics::new();
162 g.fill(Fill::Solid(Color::rgba_u8(60, 120, 220, 255)));
163 g.draw_ellipse(Vec2::new(-0.5, 0.3), Vec2::splat(0.45));
164 g.fill(Fill::Solid(Color::rgba_u8(255, 130, 70, 255)));
165 g.draw_ellipse(Vec2::new(0.5, -0.2), Vec2::splat(0.45));
166 g.fill(Fill::Solid(Color::rgba_u8(80, 220, 160, 255)));
167 g.draw_ellipse(Vec2::new(0.0, 0.0), Vec2::splat(0.40));
168 let _ = s.add_child(s.root(), g);
169 let _ = renderer.render_stage(app, rt.view(), Color::rgba(0.05, 0.05, 0.08, 1.0), &s);
170 rt
171}
172
173fn render_spotlight(
174 app: &Application,
175 renderer: &Renderer,
176 format: wgpu::TextureFormat,
177) -> RenderTexture {
178 let rt = RenderTexture::with_format(app, SIZE, SIZE, format);
181 let mut s = Stage::new();
182 let mut g = Graphics::new();
183 g.fill(Fill::Solid(Color::rgba_u8(255, 235, 200, 255)));
184 g.draw_rect(Rect::new(-1.0, -1.0, 2.0, 2.0));
185 g.fill(Fill::Solid(Color::rgba_u8(255, 180, 80, 230)));
186 g.stroke(Some(Stroke::new(0.04, Color::rgba_u8(255, 220, 100, 255))));
187 g.draw_ellipse(Vec2::new(0.0, 0.0), Vec2::splat(0.55));
188 g.stroke(None);
189 g.fill(Fill::Solid(Color::rgba_u8(0, 0, 0, 60)));
190 g.draw_rect(Rect::new(-1.0, -1.0, 2.0, 2.0));
191 let _ = s.add_child(s.root(), g);
192 if std::env::var_os("WISP_SKIP_GPU_FILTER_TESTS").is_some() {
195 let direct = RenderTexture::with_format(app, SIZE, SIZE, format);
196 let _ = renderer.render_stage(app, direct.view(), Color::TRANSPARENT, &s);
197 return direct;
198 }
199 let input = RenderTexture::with_format(app, SIZE, SIZE, format);
200 let _ = renderer.render_stage(app, input.view(), Color::TRANSPARENT, &s);
201 renderer.apply_filter(
202 app,
203 &DropShadowFilter {
204 offset: Vec2::new(0.0, 0.0),
205 blur: 4.0,
206 color: Color::rgba(1.0, 0.8, 0.4, 0.6),
207 },
208 &input,
209 &rt,
210 );
211 rt
212}