Skip to main content

polyline_gradient

Function polyline_gradient 

Source
pub fn polyline_gradient(
    points: &[Point],
    colors: &[Color],
    opts: &RibbonOptions,
) -> Mesh
Expand description

Per-vertex coloured, constant-width ribbon. colors.len() must equal points.len().

Examples found in repository?
examples/ribbon_gradient.rs (line 70)
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    // ── Render 1: colour gradient along the line ───────────────────
37    {
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        // 5-stop gradient: cool blues to warm reds.
54        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    // ── Render 2: variable width along the line ────────────────────
83    {
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        // Width ramps from 2pt to 20pt linearly.
98        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        // `polyline_ribbon_full` with no colours uses black; pass a
111        // same-colour slice for the desired stroke colour.
112        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    // ── Render 3: gradient + variable width on a self-intersecting
127    // figure-8 (Lissajous 1:2). The polyline crosses itself once at
128    // the centre — each crossing arm renders at a different width
129    // and colour, and SrcOver compositing layers them in source order
130    // (later vertices draw on top of earlier ones).
131    {
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    // ── Render 4: 3×3 cap/join grid ────────────────────────────────
190    {
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        // Build 9 ribbons in one mesh (or one render): for each
203        // (row, col), zigzag polyline showing the cap and join.
204        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                // Zigzag polyline filling the cell.
212                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    // ── Render 5: closed-loop rainbow ──────────────────────────────
240    // Regular dodecagon vertices, one full hue cycle around the
241    // loop. The wrap segment interpolates colors[n-1] → colors[0]
242    // (≈ magenta → red, a short hop on the colour wheel), which
243    // should be visually indistinguishable from the other segment
244    // transitions — i.e. no seam at the close.
245    {
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    // ── Render 6: closed loop with gradient + variable width ───────
279    // Five-lobed polar curve r(θ) = R + a·sin(5θ) sampled at 240
280    // points. Colours cycle the full hue wheel once; widths pulse
281    // at twice the lobe frequency so the ribbon visibly fattens and
282    // narrows around the loop. Both pieces close seamlessly because
283    // `polygon_*` treats the n samples as one continuous ring.
284    {
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                // Width pulses between ~4 pt and ~22 pt.
306                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, // ignored by polygon_*
313            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}