pub struct Point {
pub x: f64,
pub y: f64,
}Expand description
A 2D point.
This type represents a point in 2D space. It has the same layout as Vec2, but
its meaning is different: Vec2 represents a change in location (for example velocity).
In general, kurbo overloads math operators where it makes sense, for example implementing
Affine * Point as the point under the affine transformation. However Point + Point and
f64 * Point are not implemented, because the operations do not make geometric sense. If you
need to apply these operations, then 1) check what you’re doing makes geometric sense, then 2)
use Point::to_vec2 to convert the point to a Vec2.
Fields§
§x: f64The x coordinate.
y: f64The y coordinate.
Implementations§
Source§impl Point
impl Point
Sourcepub const fn new(x: f64, y: f64) -> Point
pub const fn new(x: f64, y: f64) -> Point
Create a new Point with the provided x and y coordinates.
Examples found in repository?
24fn cell_center(idx: usize) -> Point {
25 let col = idx % GRID_COLS;
26 let row = idx / GRID_COLS;
27 Point::new(
28 GRID_LEFT + CELL_W * (col as f64 + 0.5),
29 GRID_TOP + CELL_H * (row as f64 + 0.5),
30 )
31}
32
33fn draw_shape_centered(
34 scene: &mut impl SceneBuilder,
35 shape: &Shape,
36 center: Point,
37 size: f64,
38 brush: &Brush,
39 stroke_world_width: f64,
40) {
41 let xform = Affine::translate(center.to_vec2()) * Affine::scale(size);
42 let (paths, style) = match shape.kind() {
43 ShapeKind::Paths { paths, style } => (paths, style),
44 ShapeKind::Glyph { .. } => return,
45 };
46 match style {
47 ShapeStyle::Fill => {
48 for sub in paths {
49 scene.fill(FillRule::NonZero, xform, brush, None, sub, PickId::Skip);
50 }
51 }
52 ShapeStyle::Stroke => {
53 let stroke = Stroke::new(stroke_world_width / size)
54 .with_caps(Cap::Round)
55 .with_join(Join::Round);
56 for sub in paths {
57 scene.stroke(&stroke, xform, brush, None, sub, PickId::Skip);
58 }
59 }
60 }
61}
62
63fn draw_shape_attached(
64 scene: &mut impl SceneBuilder,
65 shape: &Shape,
66 placement: Point,
67 direction: Vec2,
68 size: f64,
69 brush: &Brush,
70 stroke_world_width: f64,
71) {
72 let perp = Vec2::new(-direction.y, direction.x);
73 let a = shape.anchor();
74 let anchor_world = direction * (a.x * size) + perp * (a.y * size);
75 let origin = placement - anchor_world;
76 let xform = Affine::translate(origin.to_vec2())
77 * Affine::rotate(direction.atan2())
78 * Affine::scale(size);
79 let (paths, style) = match shape.kind() {
80 ShapeKind::Paths { paths, style } => (paths, style),
81 ShapeKind::Glyph { .. } => return,
82 };
83 match style {
84 ShapeStyle::Fill => {
85 for sub in paths {
86 scene.fill(FillRule::NonZero, xform, brush, None, sub, PickId::Skip);
87 }
88 }
89 ShapeStyle::Stroke => {
90 let stroke = Stroke::new(stroke_world_width / size)
91 .with_caps(Cap::Round)
92 .with_join(Join::Round);
93 for sub in paths {
94 scene.stroke(&stroke, xform, brush, None, sub, PickId::Skip);
95 }
96 }
97 }
98}
99
100fn main() {
101 let mut renderer = VelloRenderer::new().expect("vello renderer init");
102 let registry = ShapeRegistry::with_builtins();
103
104 let glyph_brush: Brush = rgb8(60, 130, 220).into();
105 let chrome_brush: Brush = rgb8(80, 84, 96).into();
106 let line_brush: Brush = rgb8(190, 195, 205).into();
107
108 let glyph_world_stroke = 2.0;
109 let line_stroke = Stroke::new(2.5).with_caps(Cap::Butt);
110 let cell_stroke = Stroke::new(1.0);
111
112 {
113 let scene = renderer.scene();
114
115 for (i, name) in builtin::NAMES.iter().enumerate() {
116 let center = cell_center(i);
117 let cell = Rect::new(
118 center.x - CELL_W * 0.5 + 2.0,
119 center.y - CELL_H * 0.5 + 2.0,
120 center.x + CELL_W * 0.5 - 2.0,
121 center.y + CELL_H * 0.5 - 2.0,
122 )
123 .to_path(0.1);
124 scene.stroke(
125 &cell_stroke,
126 Affine::IDENTITY,
127 &chrome_brush,
128 None,
129 &cell,
130 PickId::Skip,
131 );
132
133 let shape = registry.get(name).expect("registered");
134 draw_shape_centered(scene, shape, center, 28.0, &glyph_brush, glyph_world_stroke);
135 }
136
137 let demos: &[(&str, f64)] = &[
138 ("arrow-closed", 18.0),
139 ("arrow-stealth", 22.0),
140 ("arrow-open", 18.0),
141 ("arrow-feather", 28.0),
142 ("arrow-dot", 14.0),
143 ];
144 let demo_top = GRID_TOP + (GRID_ROWS as f64) * CELL_H + 25.0;
145 let demo_spacing = 28.0;
146 let x_start = 60.0;
147 let x_end = (W as f64) - 60.0;
148
149 for (i, &(name, size)) in demos.iter().enumerate() {
150 let y = demo_top + (i as f64) * demo_spacing;
151 let start = Point::new(x_start, y);
152 let end = Point::new(x_end, y);
153
154 let direction = (end - start).normalize();
155 let shape = registry.get(name).expect("registered");
156
157 let mut line = hephaestus::Path::new();
158 line.move_to(start);
159 line.line_to(end);
160 scene.stroke(
161 &line_stroke,
162 Affine::IDENTITY,
163 &line_brush,
164 None,
165 &line,
166 PickId::Skip,
167 );
168
169 draw_shape_attached(
170 scene,
171 shape,
172 end,
173 direction,
174 size,
175 &glyph_brush,
176 glyph_world_stroke,
177 );
178 }
179 }
180
181 let mut pixels = vec![0u8; (W * H * 4) as usize];
182 let bg: Color = rgb8(20, 22, 28);
183 renderer
184 .render_to_buffer(W, H, bg, &mut pixels)
185 .expect("render");
186
187 let path = std::env::current_dir()
188 .unwrap()
189 .join("examples/shapes_demo.png");
190 hephaestus::image::write_png(&path, W, H, &pixels).expect("write png");
191 println!("wrote {}", path.display());
192}More examples
14fn main() {
15 let mut renderer = VelloRenderer::new().expect("vello renderer init");
16 let (w, h) = (512u32, 512u32);
17
18 {
19 let scene = renderer.scene();
20
21 // 1. Filled rectangle (solid brush)
22 let rect = Rect::new(40.0, 40.0, 240.0, 200.0).to_path(0.1);
23 let solid: Brush = rgb8(60, 120, 200).into();
24 scene.fill(
25 FillRule::NonZero,
26 Affine::IDENTITY,
27 &solid,
28 None,
29 &rect,
30 PickId::Skip,
31 );
32
33 // 2. Stroked open polyline with round caps/joins
34 let mut poly = Path::new();
35 poly.move_to(Point::new(60.0, 300.0));
36 poly.line_to(Point::new(140.0, 360.0));
37 poly.line_to(Point::new(200.0, 280.0));
38 poly.line_to(Point::new(260.0, 380.0));
39 let stroke = Stroke::new(8.0)
40 .with_caps(Cap::Round)
41 .with_join(Join::Round);
42 let line_brush: Brush = rgb8(220, 220, 220).into();
43 scene.stroke(
44 &stroke,
45 Affine::IDENTITY,
46 &line_brush,
47 None,
48 &poly,
49 PickId::Skip,
50 );
51
52 // 3. Gradient-filled circle
53 let circle = kurbo::Circle::new(Point::new(380.0, 150.0), 90.0).to_path(0.1);
54 let gradient = Gradient::new_linear(Point::new(290.0, 60.0), Point::new(470.0, 240.0))
55 .with_stops([rgb(0.95, 0.55, 0.15), rgb(0.15, 0.05, 0.45)].as_slice());
56 let grad_brush: Brush = gradient.into();
57 scene.fill(
58 FillRule::NonZero,
59 Affine::IDENTITY,
60 &grad_brush,
61 None,
62 &circle,
63 PickId::Skip,
64 );
65
66 // 4. Multiply layer with a translucent square overlapping the rect
67 let layer_clip = Rect::new(0.0, 0.0, w as f64, h as f64).to_path(0.1);
68 scene.push_layer(
69 BlendMode::new(Mix::Multiply, Compose::SrcOver),
70 1.0,
71 Affine::IDENTITY,
72 &layer_clip,
73 );
74 let overlap = Rect::new(160.0, 120.0, 360.0, 320.0).to_path(0.1);
75 let overlap_brush: Brush = rgba(1.0, 0.85, 0.2, 0.7).into();
76 scene.fill(
77 FillRule::NonZero,
78 Affine::IDENTITY,
79 &overlap_brush,
80 None,
81 &overlap,
82 PickId::Skip,
83 );
84 scene.pop_layer();
85 }
86
87 let mut pixels = vec![0u8; (w * h * 4) as usize];
88 let bg: Color = rgb8(20, 22, 28);
89 renderer
90 .render_to_buffer(w, h, bg, &mut pixels)
91 .expect("render");
92
93 let path = std::env::current_dir().unwrap().join("examples/hello.png");
94 hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
95 println!("wrote {}", path.display());
96}16fn main() {
17 let mut renderer = HybridRenderer::new().expect("hybrid renderer init");
18 let (w, h) = (512u32, 512u32);
19
20 {
21 let scene = renderer.scene();
22
23 // 1. Filled rectangle (solid brush)
24 let rect = Rect::new(40.0, 40.0, 240.0, 200.0).to_path(0.1);
25 let solid: Brush = rgb8(60, 120, 200).into();
26 scene.fill(
27 FillRule::NonZero,
28 Affine::IDENTITY,
29 &solid,
30 None,
31 &rect,
32 PickId::Skip,
33 );
34
35 // 2. Stroked open polyline with round caps/joins
36 let mut poly = Path::new();
37 poly.move_to(Point::new(60.0, 300.0));
38 poly.line_to(Point::new(140.0, 360.0));
39 poly.line_to(Point::new(200.0, 280.0));
40 poly.line_to(Point::new(260.0, 380.0));
41 let stroke = Stroke::new(8.0)
42 .with_caps(Cap::Round)
43 .with_join(Join::Round);
44 let line_brush: Brush = rgb8(220, 220, 220).into();
45 scene.stroke(
46 &stroke,
47 Affine::IDENTITY,
48 &line_brush,
49 None,
50 &poly,
51 PickId::Skip,
52 );
53
54 // 3. Gradient-filled circle
55 let circle = kurbo::Circle::new(Point::new(380.0, 150.0), 90.0).to_path(0.1);
56 let gradient = Gradient::new_linear(Point::new(290.0, 60.0), Point::new(470.0, 240.0))
57 .with_stops([rgb(0.95, 0.55, 0.15), rgb(0.15, 0.05, 0.45)].as_slice());
58 let grad_brush: Brush = gradient.into();
59 scene.fill(
60 FillRule::NonZero,
61 Affine::IDENTITY,
62 &grad_brush,
63 None,
64 &circle,
65 PickId::Skip,
66 );
67
68 // 4. Multiply layer with a translucent square overlapping the rect
69 let layer_clip = Rect::new(0.0, 0.0, w as f64, h as f64).to_path(0.1);
70 scene.push_layer(
71 BlendMode::new(Mix::Multiply, Compose::SrcOver),
72 1.0,
73 Affine::IDENTITY,
74 &layer_clip,
75 );
76 let overlap = Rect::new(160.0, 120.0, 360.0, 320.0).to_path(0.1);
77 let overlap_brush: Brush = rgba(1.0, 0.85, 0.2, 0.7).into();
78 scene.fill(
79 FillRule::NonZero,
80 Affine::IDENTITY,
81 &overlap_brush,
82 None,
83 &overlap,
84 PickId::Skip,
85 );
86 scene.pop_layer();
87 }
88
89 let mut pixels = vec![0u8; (w * h * 4) as usize];
90 let bg: Color = rgb8(20, 22, 28);
91 renderer
92 .render_to_buffer(w, h, bg, &mut pixels)
93 .expect("render");
94
95 let path = std::env::current_dir()
96 .unwrap()
97 .join("examples/hello_hybrid.png");
98 hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
99 println!("wrote {}", path.display());
100}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}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}Sourcepub const fn to_vec2(self) -> Vec2
pub const fn to_vec2(self) -> Vec2
Convert this point into a Vec2.
Examples found in repository?
33fn draw_shape_centered(
34 scene: &mut impl SceneBuilder,
35 shape: &Shape,
36 center: Point,
37 size: f64,
38 brush: &Brush,
39 stroke_world_width: f64,
40) {
41 let xform = Affine::translate(center.to_vec2()) * Affine::scale(size);
42 let (paths, style) = match shape.kind() {
43 ShapeKind::Paths { paths, style } => (paths, style),
44 ShapeKind::Glyph { .. } => return,
45 };
46 match style {
47 ShapeStyle::Fill => {
48 for sub in paths {
49 scene.fill(FillRule::NonZero, xform, brush, None, sub, PickId::Skip);
50 }
51 }
52 ShapeStyle::Stroke => {
53 let stroke = Stroke::new(stroke_world_width / size)
54 .with_caps(Cap::Round)
55 .with_join(Join::Round);
56 for sub in paths {
57 scene.stroke(&stroke, xform, brush, None, sub, PickId::Skip);
58 }
59 }
60 }
61}
62
63fn draw_shape_attached(
64 scene: &mut impl SceneBuilder,
65 shape: &Shape,
66 placement: Point,
67 direction: Vec2,
68 size: f64,
69 brush: &Brush,
70 stroke_world_width: f64,
71) {
72 let perp = Vec2::new(-direction.y, direction.x);
73 let a = shape.anchor();
74 let anchor_world = direction * (a.x * size) + perp * (a.y * size);
75 let origin = placement - anchor_world;
76 let xform = Affine::translate(origin.to_vec2())
77 * Affine::rotate(direction.atan2())
78 * Affine::scale(size);
79 let (paths, style) = match shape.kind() {
80 ShapeKind::Paths { paths, style } => (paths, style),
81 ShapeKind::Glyph { .. } => return,
82 };
83 match style {
84 ShapeStyle::Fill => {
85 for sub in paths {
86 scene.fill(FillRule::NonZero, xform, brush, None, sub, PickId::Skip);
87 }
88 }
89 ShapeStyle::Stroke => {
90 let stroke = Stroke::new(stroke_world_width / size)
91 .with_caps(Cap::Round)
92 .with_join(Join::Round);
93 for sub in paths {
94 scene.stroke(&stroke, xform, brush, None, sub, PickId::Skip);
95 }
96 }
97 }
98}Sourcepub fn distance(self, other: Point) -> f64
pub fn distance(self, other: Point) -> f64
Euclidean distance.
See Vec2::hypot for the same operation on Vec2.
Sourcepub fn distance_squared(self, other: Point) -> f64
pub fn distance_squared(self, other: Point) -> f64
Squared Euclidean distance.
See Vec2::hypot2 for the same operation on Vec2.
Sourcepub fn ceil(self) -> Point
pub fn ceil(self) -> Point
Returns a new Point,
with x and y rounded up to the nearest integer,
unless they are already an integer.
§Examples
use kurbo::Point;
let a = Point::new(3.3, 3.6).ceil();
let b = Point::new(3.0, -3.1).ceil();
assert_eq!(a.x, 4.0);
assert_eq!(a.y, 4.0);
assert_eq!(b.x, 3.0);
assert_eq!(b.y, -3.0);Sourcepub fn floor(self) -> Point
pub fn floor(self) -> Point
Returns a new Point,
with x and y rounded down to the nearest integer,
unless they are already an integer.
§Examples
use kurbo::Point;
let a = Point::new(3.3, 3.6).floor();
let b = Point::new(3.0, -3.1).floor();
assert_eq!(a.x, 3.0);
assert_eq!(a.y, 3.0);
assert_eq!(b.x, 3.0);
assert_eq!(b.y, -4.0);Sourcepub fn expand(self) -> Point
pub fn expand(self) -> Point
Returns a new Point,
with x and y rounded away from zero to the nearest integer,
unless they are already an integer.
§Examples
use kurbo::Point;
let a = Point::new(3.3, 3.6).expand();
let b = Point::new(3.0, -3.1).expand();
assert_eq!(a.x, 4.0);
assert_eq!(a.y, 4.0);
assert_eq!(b.x, 3.0);
assert_eq!(b.y, -4.0);Sourcepub fn trunc(self) -> Point
pub fn trunc(self) -> Point
Returns a new Point,
with x and y rounded towards zero to the nearest integer,
unless they are already an integer.
§Examples
use kurbo::Point;
let a = Point::new(3.3, 3.6).trunc();
let b = Point::new(3.0, -3.1).trunc();
assert_eq!(a.x, 3.0);
assert_eq!(a.y, 3.0);
assert_eq!(b.x, 3.0);
assert_eq!(b.y, -3.0);Sourcepub const fn get_coord_mut(&mut self, axis: Axis) -> &mut f64
pub const fn get_coord_mut(&mut self, axis: Axis) -> &mut f64
Get a mutable reference to the member matching the given axis.
Trait Implementations§
Source§impl AddAssign<Vec2> for Point
impl AddAssign<Vec2> for Point
Source§fn add_assign(&mut self, other: Vec2)
fn add_assign(&mut self, other: Vec2)
+= operation. Read moreimpl Copy for Point
impl StructuralPartialEq for Point
Auto Trait Implementations§
impl Freeze for Point
impl RefUnwindSafe for Point
impl Send for Point
impl Sync for Point
impl Unpin for Point
impl UnsafeUnpin for Point
impl UnwindSafe for Point
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<T> Brush for T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.