Skip to main content

primitives_demo/
primitives_demo.rs

1//! Visual demo of the `primitives` module.
2//!
3//! - Top strip: end-clipped, Chaikin-rounded polyline between two nodes.
4//! - Middle strip: polygon-with-hole, offset outward + Chaikin-rounded per
5//!   output ring.
6//! - Bottom strip: wedge & annular wedge with `round_path_corners` —
7//!   curve-aware fillets at the line-to-arc corners.
8//!
9//! Renders to `examples/primitives_demo.png`.
10
11use hephaestus::backend::vello::VelloRenderer;
12use hephaestus::color::{rgb8, Color};
13use hephaestus::stroke::{Cap, Join, Stroke};
14use hephaestus::{
15    annular_wedge, clip_polyline, offset_polygon, polygon, round_corners, round_path_corners,
16    wedge, Affine, Brush, CornerRounding, EndClip, FillRule, Path, PickId, Point, PolygonOptions,
17    Rect, Renderer, SceneBuilder,
18};
19use kurbo::Shape;
20use std::f64::consts::PI;
21
22fn main() {
23    let mut renderer = VelloRenderer::new().expect("vello renderer init");
24    let (w, h) = (640u32, 800u32);
25
26    {
27        let scene = renderer.scene();
28        let reference_brush: Brush = rgb8(80, 80, 80).into();
29        let dotted = Stroke::new(1.0).with_dashes(0.0, vec![3.0, 4.0]);
30
31        // ---------- strip 1: end-clipped, corner-rounded polyline ----------
32
33        let node_a_center = Point::new(120.0, 140.0);
34        let node_a_radius = 40.0;
35        let node_b = Rect::new(440.0, 100.0, 580.0, 180.0);
36
37        let node_brush: Brush = rgb8(60, 90, 140).into();
38        scene.fill(
39            FillRule::NonZero,
40            Affine::IDENTITY,
41            &node_brush,
42            None,
43            &kurbo::Circle::new(node_a_center, node_a_radius).to_path(0.1),
44            PickId::Skip,
45        );
46        scene.fill(
47            FillRule::NonZero,
48            Affine::IDENTITY,
49            &node_brush,
50            None,
51            &node_b.to_path(0.1),
52            PickId::Skip,
53        );
54
55        let raw = [
56            node_a_center,
57            Point::new(220.0, 60.0),
58            Point::new(340.0, 220.0),
59            Point::new(420.0, 80.0),
60            Point::new(node_b.center().x, node_b.center().y),
61        ];
62        let clipped = clip_polyline(
63            &raw,
64            Some(EndClip::Circle {
65                center: node_a_center,
66                radius: node_a_radius,
67            }),
68            Some(EndClip::Rect(node_b)),
69        );
70        let connector = round_corners(
71            &clipped,
72            false,
73            CornerRounding {
74                max_cut: 30.0,
75                ..Default::default()
76            },
77        );
78        let line_stroke = Stroke::new(4.0)
79            .with_caps(Cap::Round)
80            .with_join(Join::Round);
81        let line_brush: Brush = rgb8(230, 200, 120).into();
82        scene.stroke(
83            &line_stroke,
84            Affine::IDENTITY,
85            &line_brush,
86            None,
87            &connector,
88            PickId::Skip,
89        );
90        scene.stroke(
91            &dotted,
92            Affine::IDENTITY,
93            &reference_brush,
94            None,
95            &mk_polyline(&raw),
96            PickId::Skip,
97        );
98
99        // ---------- strip 2: polygon-with-hole, offset + rounded ----------
100
101        let outer = [
102            Point::new(120.0, 320.0),
103            Point::new(520.0, 320.0),
104            Point::new(520.0, 500.0),
105            Point::new(120.0, 500.0),
106        ];
107        let hole = [
108            Point::new(240.0, 370.0),
109            Point::new(400.0, 370.0),
110            Point::new(400.0, 450.0),
111            Point::new(240.0, 450.0),
112        ];
113        let rings: [&[Point]; 2] = [&outer, &hole];
114
115        let plain = polygon(&rings, PolygonOptions::default());
116        scene.stroke(
117            &dotted,
118            Affine::IDENTITY,
119            &reference_brush,
120            None,
121            &plain,
122            PickId::Skip,
123        );
124
125        let inflated_rings = offset_polygon(&rings, 15.0, 4.0);
126        let mut inflated = Path::new();
127        for r in &inflated_rings {
128            let sub = round_corners(r, true, CornerRounding::default());
129            for el in sub.iter() {
130                inflated.push(el);
131            }
132        }
133        let fill_brush: Brush = rgb8(180, 70, 90).into();
134        scene.fill(
135            FillRule::NonZero,
136            Affine::IDENTITY,
137            &fill_brush,
138            None,
139            &inflated,
140            PickId::Skip,
141        );
142
143        // ---------- strip 3: wedge & annular wedge with curve-aware rounding ----------
144
145        // Left: wedge with rounded line-to-arc corners.
146        let w_center = Point::new(170.0, 680.0);
147        let w_radius = 90.0;
148        let plain_wedge = wedge(w_center, w_radius, -PI / 2.0 - PI / 5.0, PI * 2.0 / 5.0);
149        scene.stroke(
150            &dotted,
151            Affine::IDENTITY,
152            &reference_brush,
153            None,
154            &plain_wedge,
155            PickId::Skip,
156        );
157        let rounded_wedge = round_path_corners(
158            &plain_wedge,
159            CornerRounding {
160                max_cut: 18.0,
161                ..Default::default()
162            },
163        );
164        let wedge_brush: Brush = rgb8(110, 160, 90).into();
165        scene.fill(
166            FillRule::NonZero,
167            Affine::IDENTITY,
168            &wedge_brush,
169            None,
170            &rounded_wedge,
171            PickId::Skip,
172        );
173
174        // Right: annular wedge with all four corners rounded.
175        let a_center = Point::new(450.0, 680.0);
176        let plain_aw = annular_wedge(a_center, 35.0, 100.0, -PI / 2.0 - PI / 4.0, PI / 2.0);
177        scene.stroke(
178            &dotted,
179            Affine::IDENTITY,
180            &reference_brush,
181            None,
182            &plain_aw,
183            PickId::Skip,
184        );
185        let rounded_aw = round_path_corners(
186            &plain_aw,
187            CornerRounding {
188                max_cut: 14.0,
189                ..Default::default()
190            },
191        );
192        let aw_brush: Brush = rgb8(180, 130, 90).into();
193        scene.fill(
194            FillRule::NonZero,
195            Affine::IDENTITY,
196            &aw_brush,
197            None,
198            &rounded_aw,
199            PickId::Skip,
200        );
201    }
202
203    let mut pixels = vec![0u8; (w * h * 4) as usize];
204    let bg: Color = rgb8(20, 22, 28);
205    renderer
206        .render_to_buffer(w, h, bg, &mut pixels)
207        .expect("render");
208
209    let path = std::env::current_dir()
210        .unwrap()
211        .join("examples/primitives_demo.png");
212    hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
213    println!("wrote {}", path.display());
214}
215
216fn mk_polyline(pts: &[Point]) -> Path {
217    let mut p = Path::new();
218    p.move_to(pts[0]);
219    for v in &pts[1..] {
220        p.line_to(*v);
221    }
222    p
223}