1use hephaestus::backend::vello::VelloRenderer;
19use hephaestus::color::{rgb8, Color};
20use hephaestus::geometry::Affine;
21use hephaestus::mesh::Mesh;
22use hephaestus::pick::PickId;
23use hephaestus::primitives::{
24 polygon_gradient, polygon_ribbon_full, polyline_gradient, polyline_ribbon,
25 polyline_ribbon_full, RibbonOptions,
26};
27use hephaestus::scene::SceneBuilder;
28use hephaestus::stroke::{Cap, Join};
29use hephaestus::{Point, Renderer};
30
31fn main() {
32 let dpi = 96.0;
33 let bg: Color = rgb8(248, 248, 252);
34 let mut renderer = VelloRenderer::new().expect("vello renderer init");
35
36 {
38 let (w, h) = (1200u32, 300u32);
39 let n = 80;
40 let xs: Vec<f64> = (0..n)
41 .map(|i| 80.0 + i as f64 / (n - 1) as f64 * 1040.0)
42 .collect();
43 let ys: Vec<f64> = xs
44 .iter()
45 .enumerate()
46 .map(|(i, _)| 150.0 + 60.0 * (i as f64 * 0.18).sin())
47 .collect();
48 let points: Vec<Point> = xs
49 .iter()
50 .zip(ys.iter())
51 .map(|(x, y)| Point::new(*x, *y))
52 .collect();
53 let stops = [
55 rgb8(60, 80, 200),
56 rgb8(80, 160, 200),
57 rgb8(220, 220, 220),
58 rgb8(220, 130, 80),
59 rgb8(200, 50, 60),
60 ];
61 let colors: Vec<Color> = (0..n)
62 .map(|i| interpolate_stops(&stops, i as f64 / (n - 1) as f64))
63 .collect();
64 let opts = RibbonOptions {
65 half_width: pt_to_px(12.0, dpi) * 0.5,
66 cap: Cap::Round,
67 join: Join::Round,
68 miter_limit: 4.0,
69 };
70 let mesh = polyline_gradient(&points, &colors, &opts);
71 render_mesh(
72 &mut renderer,
73 &mesh,
74 w,
75 h,
76 dpi,
77 bg,
78 "examples/ribbon_1_gradient_along.png",
79 );
80 }
81
82 {
84 let (w, h) = (1200u32, 300u32);
85 let n = 80;
86 let xs: Vec<f64> = (0..n)
87 .map(|i| 80.0 + i as f64 / (n - 1) as f64 * 1040.0)
88 .collect();
89 let ys: Vec<f64> = (0..n)
90 .map(|i| 150.0 + 60.0 * (i as f64 * 0.18).sin())
91 .collect();
92 let points: Vec<Point> = xs
93 .iter()
94 .zip(ys.iter())
95 .map(|(x, y)| Point::new(*x, *y))
96 .collect();
97 let half_widths: Vec<f64> = (0..n)
99 .map(|i| {
100 let t = i as f64 / (n - 1) as f64;
101 pt_to_px(2.0 + (20.0 - 2.0) * t, dpi) * 0.5
102 })
103 .collect();
104 let opts = RibbonOptions {
105 half_width: 1.0,
106 cap: Cap::Round,
107 join: Join::Round,
108 miter_limit: 4.0,
109 };
110 let stroke = rgb8(40, 90, 180);
113 let colors = vec![stroke; n];
114 let mesh = polyline_ribbon_full(&points, Some(&colors), Some(&half_widths), &opts);
115 render_mesh(
116 &mut renderer,
117 &mesh,
118 w,
119 h,
120 dpi,
121 bg,
122 "examples/ribbon_2_variable_width.png",
123 );
124 }
125
126 {
132 let (w, h) = (1200u32, 400u32);
133 let n = 240;
134 let cx = 600.0;
135 let cy = 200.0;
136 let amp_x = 500.0;
137 let amp_y = 120.0;
138 let xs: Vec<f64> = (0..n)
139 .map(|i| {
140 let t = i as f64 / (n - 1) as f64 * std::f64::consts::TAU;
141 cx + amp_x * t.sin()
142 })
143 .collect();
144 let ys: Vec<f64> = (0..n)
145 .map(|i| {
146 let t = i as f64 / (n - 1) as f64 * std::f64::consts::TAU;
147 cy + amp_y * (2.0 * t).sin()
148 })
149 .collect();
150 let points: Vec<Point> = xs
151 .iter()
152 .zip(ys.iter())
153 .map(|(x, y)| Point::new(*x, *y))
154 .collect();
155 let stops = [
156 rgb8(60, 80, 200),
157 rgb8(80, 160, 200),
158 rgb8(220, 220, 220),
159 rgb8(220, 130, 80),
160 rgb8(200, 50, 60),
161 ];
162 let colors: Vec<Color> = (0..n)
163 .map(|i| interpolate_stops(&stops, i as f64 / (n - 1) as f64))
164 .collect();
165 let half_widths: Vec<f64> = (0..n)
166 .map(|i| {
167 let t = i as f64 / (n - 1) as f64;
168 pt_to_px(2.0 + (24.0 - 2.0) * t, dpi) * 0.5
169 })
170 .collect();
171 let opts = RibbonOptions {
172 half_width: 1.0,
173 cap: Cap::Round,
174 join: Join::Round,
175 miter_limit: 4.0,
176 };
177 let mesh = polyline_ribbon_full(&points, Some(&colors), Some(&half_widths), &opts);
178 render_mesh(
179 &mut renderer,
180 &mesh,
181 w,
182 h,
183 dpi,
184 bg,
185 "examples/ribbon_3_full.png",
186 );
187 }
188
189 {
191 let (w, h) = (900u32, 700u32);
192 let caps = [
193 ("butt", Cap::Butt),
194 ("square", Cap::Square),
195 ("round", Cap::Round),
196 ];
197 let joins = [
198 ("miter", Join::Miter),
199 ("bevel", Join::Bevel),
200 ("round", Join::Round),
201 ];
202 let mut all_meshes: Vec<Mesh> = Vec::new();
205 let cell_w = w as f64 / 3.0;
206 let cell_h = h as f64 / 3.0;
207 for (r_idx, (_cap_name, cap)) in caps.iter().enumerate() {
208 for (c_idx, (_join_name, join)) in joins.iter().enumerate() {
209 let cx = c_idx as f64 * cell_w;
210 let cy = r_idx as f64 * cell_h;
211 let pad = 30.0;
213 let pts = vec![
214 Point::new(cx + pad, cy + cell_h - pad),
215 Point::new(cx + cell_w * 0.5, cy + pad),
216 Point::new(cx + cell_w - pad, cy + cell_h - pad),
217 ];
218 let opts = RibbonOptions {
219 half_width: pt_to_px(18.0, dpi) * 0.5,
220 cap: *cap,
221 join: *join,
222 miter_limit: 4.0,
223 };
224 let stroke = rgb8(40, 90, 180);
225 all_meshes.push(polyline_ribbon(&pts, stroke, &opts));
226 }
227 }
228 render_meshes(
229 &mut renderer,
230 &all_meshes,
231 w,
232 h,
233 dpi,
234 bg,
235 "examples/ribbon_4_caps_joins.png",
236 );
237 }
238
239 {
246 let (w, h) = (700u32, 700u32);
247 let cx = 350.0;
248 let cy = 350.0;
249 let radius = 260.0;
250 let n = 12;
251 let points: Vec<Point> = (0..n)
252 .map(|i| {
253 let theta = i as f64 / n as f64 * std::f64::consts::TAU;
254 Point::new(cx + radius * theta.cos(), cy + radius * theta.sin())
255 })
256 .collect();
257 let colors: Vec<Color> = (0..n)
258 .map(|i| hsv_to_color(i as f64 / n as f64, 0.85, 0.95))
259 .collect();
260 let opts = RibbonOptions {
261 half_width: pt_to_px(28.0, dpi) * 0.5,
262 cap: Cap::Butt,
263 join: Join::Round,
264 miter_limit: 4.0,
265 };
266 let mesh = polygon_gradient(&points, &colors, &opts);
267 render_mesh(
268 &mut renderer,
269 &mesh,
270 w,
271 h,
272 dpi,
273 bg,
274 "examples/ribbon_5_closed_rainbow.png",
275 );
276 }
277
278 {
285 let (w, h) = (900u32, 900u32);
286 let cx = 450.0;
287 let cy = 450.0;
288 let base_r = 280.0;
289 let amp_r = 70.0;
290 let lobes = 5.0;
291 let n = 240;
292 let points: Vec<Point> = (0..n)
293 .map(|i| {
294 let theta = i as f64 / n as f64 * std::f64::consts::TAU;
295 let r = base_r + amp_r * (lobes * theta).sin();
296 Point::new(cx + r * theta.cos(), cy + r * theta.sin())
297 })
298 .collect();
299 let colors: Vec<Color> = (0..n)
300 .map(|i| hsv_to_color(i as f64 / n as f64, 0.85, 0.95))
301 .collect();
302 let half_widths: Vec<f64> = (0..n)
303 .map(|i| {
304 let theta = i as f64 / n as f64 * std::f64::consts::TAU;
305 let pt = 13.0 + 9.0 * (2.0 * lobes * theta).sin();
307 pt_to_px(pt, dpi) * 0.5
308 })
309 .collect();
310 let opts = RibbonOptions {
311 half_width: 1.0,
312 cap: Cap::Butt, join: Join::Round,
314 miter_limit: 4.0,
315 };
316 let mesh = polygon_ribbon_full(&points, Some(&colors), Some(&half_widths), &opts);
317 render_mesh(
318 &mut renderer,
319 &mesh,
320 w,
321 h,
322 dpi,
323 bg,
324 "examples/ribbon_6_closed_full.png",
325 );
326 }
327}
328
329fn hsv_to_color(h: f64, s: f64, v: f64) -> Color {
333 let h6 = (h.rem_euclid(1.0)) * 6.0;
334 let c = v * s;
335 let x = c * (1.0 - ((h6 % 2.0) - 1.0).abs());
336 let m = v - c;
337 let (r, g, b) = match h6 as u32 {
338 0 => (c, x, 0.0),
339 1 => (x, c, 0.0),
340 2 => (0.0, c, x),
341 3 => (0.0, x, c),
342 4 => (x, 0.0, c),
343 _ => (c, 0.0, x),
344 };
345 Color::new([(r + m) as f32, (g + m) as f32, (b + m) as f32, 1.0])
346}
347
348fn pt_to_px(pt: f64, dpi: f64) -> f64 {
349 pt * dpi / 72.0
350}
351
352fn interpolate_stops(stops: &[Color], t: f64) -> Color {
353 if stops.is_empty() {
354 return Color::new([0.0, 0.0, 0.0, 1.0]);
355 }
356 if stops.len() == 1 {
357 return stops[0];
358 }
359 let scaled = t.clamp(0.0, 1.0) * (stops.len() - 1) as f64;
360 let lo = scaled.floor() as usize;
361 let hi = (lo + 1).min(stops.len() - 1);
362 let frac = (scaled - lo as f64) as f32;
363 let a = stops[lo].components;
364 let b = stops[hi].components;
365 Color::new([
366 a[0] * (1.0 - frac) + b[0] * frac,
367 a[1] * (1.0 - frac) + b[1] * frac,
368 a[2] * (1.0 - frac) + b[2] * frac,
369 a[3] * (1.0 - frac) + b[3] * frac,
370 ])
371}
372
373fn render_mesh(
374 renderer: &mut VelloRenderer,
375 mesh: &Mesh,
376 w: u32,
377 h: u32,
378 dpi: f64,
379 bg: Color,
380 out: &str,
381) {
382 let _ = dpi;
383 {
384 let scene = renderer.scene();
385 scene.clear();
386 scene.draw_mesh(mesh, Affine::IDENTITY, PickId::Skip);
387 }
388 let mut pixels = vec![0u8; (w * h * 4) as usize];
389 renderer
390 .render_to_buffer(w, h, bg, &mut pixels)
391 .expect("render");
392 let path = std::env::current_dir().unwrap().join(out);
393 hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
394 println!("wrote {}", path.display());
395}
396
397fn render_meshes(
398 renderer: &mut VelloRenderer,
399 meshes: &[Mesh],
400 w: u32,
401 h: u32,
402 dpi: f64,
403 bg: Color,
404 out: &str,
405) {
406 let _ = dpi;
407 {
408 let scene = renderer.scene();
409 scene.clear();
410 for m in meshes {
411 scene.draw_mesh(m, Affine::IDENTITY, PickId::Skip);
412 }
413 }
414 let mut pixels = vec![0u8; (w * h * 4) as usize];
415 renderer
416 .render_to_buffer(w, h, bg, &mut pixels)
417 .expect("render");
418 let path = std::env::current_dir().unwrap().join(out);
419 hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
420 println!("wrote {}", path.display());
421}