pub fn polyline_gradient(
points: &[Point],
colors: &[Color],
opts: &RibbonOptions,
) -> MeshExpand 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}