1use crate::{Canvas, Color, Point, Rect, StrokeStyle};
27use llimphi_ui::llimphi_raster::kurbo::{Affine, BezPath, Line, Rect as KurboRect, Stroke};
28use llimphi_ui::llimphi_raster::peniko::{Color as PenikoColor, Fill};
29use llimphi_ui::llimphi_raster::vello::Scene;
30use llimphi_ui::llimphi_text::{draw_block, TextBlock, Typesetter};
31
32pub struct SceneCanvas<'a> {
37 scene: &'a mut Scene,
38 typesetter: &'a mut Typesetter,
39}
40
41impl<'a> SceneCanvas<'a> {
42 pub fn new(scene: &'a mut Scene, typesetter: &'a mut Typesetter) -> Self {
43 Self { scene, typesetter }
44 }
45}
46
47fn to_peniko(c: Color) -> PenikoColor {
52 let to_byte = |x: f32| (x.clamp(0.0, 1.0) * 255.0).round() as u8;
53 PenikoColor::from_rgba8(to_byte(c.r), to_byte(c.g), to_byte(c.b), to_byte(c.a))
54}
55
56fn avg_color(cs: &[Option<Color>]) -> Color {
57 let mut acc = Color::rgba(0.0, 0.0, 0.0, 0.0);
58 let mut n = 0.0;
59 for c in cs.iter().flatten() {
60 acc.r += c.r;
61 acc.g += c.g;
62 acc.b += c.b;
63 acc.a += c.a;
64 n += 1.0;
65 }
66 if n == 0.0 {
67 return Color::TRANSPARENT;
68 }
69 Color::rgba(acc.r / n, acc.g / n, acc.b / n, acc.a / n)
70}
71
72fn to_kurbo_rect(r: Rect) -> KurboRect {
73 KurboRect::new(
74 r.x as f64,
75 r.y as f64,
76 (r.x + r.w) as f64,
77 (r.y + r.h) as f64,
78 )
79}
80
81impl<'a> Canvas for SceneCanvas<'a> {
82 fn push_clip(&mut self, _rect: Rect) {
83 }
85 fn pop_clip(&mut self) {}
86
87 fn fill_rect(&mut self, rect: Rect, color: Color) {
88 self.scene.fill(
89 Fill::NonZero,
90 Affine::IDENTITY,
91 to_peniko(color),
92 None,
93 &to_kurbo_rect(rect),
94 );
95 }
96
97 fn stroke_rect(&mut self, rect: Rect, stroke: StrokeStyle) {
98 self.scene.stroke(
99 &Stroke::new(stroke.width as f64),
100 Affine::IDENTITY,
101 to_peniko(stroke.color),
102 None,
103 &to_kurbo_rect(rect),
104 );
105 }
106
107 fn stroke_line(&mut self, a: Point, b: Point, stroke: StrokeStyle) {
108 let line = Line::new((a.x as f64, a.y as f64), (b.x as f64, b.y as f64));
109 self.scene.stroke(
110 &Stroke::new(stroke.width as f64),
111 Affine::IDENTITY,
112 to_peniko(stroke.color),
113 None,
114 &line,
115 );
116 }
117
118 fn stroke_polyline(&mut self, coords: &[f32], stroke: StrokeStyle) {
119 if coords.len() < 4 {
120 return; }
122 let mut path = BezPath::new();
123 path.move_to((coords[0] as f64, coords[1] as f64));
124 let mut i = 2;
125 while i + 1 < coords.len() {
126 path.line_to((coords[i] as f64, coords[i + 1] as f64));
127 i += 2;
128 }
129 self.scene.stroke(
130 &Stroke::new(stroke.width as f64),
131 Affine::IDENTITY,
132 to_peniko(stroke.color),
133 None,
134 &path,
135 );
136 }
137
138 fn fill_triangle_strip(&mut self, coords: &[f32], colors: &[Color]) {
139 let n = coords.len() / 2;
140 if n < 3 {
141 return;
142 }
143 for t in 0..n - 2 {
144 let i0 = t;
145 let i1 = t + 1;
146 let i2 = t + 2;
147 let avg = avg_color(&[
148 colors.get(i0).copied(),
149 colors.get(i1).copied(),
150 colors.get(i2).copied(),
151 ]);
152 let mut path = BezPath::new();
153 path.move_to((coords[i0 * 2] as f64, coords[i0 * 2 + 1] as f64));
154 path.line_to((coords[i1 * 2] as f64, coords[i1 * 2 + 1] as f64));
155 path.line_to((coords[i2 * 2] as f64, coords[i2 * 2 + 1] as f64));
156 path.close_path();
157 self.scene.fill(
158 Fill::NonZero,
159 Affine::IDENTITY,
160 to_peniko(avg),
161 None,
162 &path,
163 );
164 }
165 }
166
167 fn draw_text(&mut self, p: Point, text: &str, color: Color, size_px: f32) {
168 if text.is_empty() {
169 return;
170 }
171 let block = TextBlock::simple(text, size_px, to_peniko(color), (p.x as f64, p.y as f64));
175 draw_block(self.scene, self.typesetter, &block);
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn rojo_redondea_a_255() {
185 let c = to_peniko(Color::rgb(1.0, 0.0, 0.0)).to_rgba8();
186 assert_eq!((c.r, c.g, c.b, c.a), (255, 0, 0, 255));
187 }
188
189 #[test]
190 fn alpha_pasa_directo() {
191 let c = to_peniko(Color::rgba(0.0, 0.0, 1.0, 0.25)).to_rgba8();
192 assert_eq!(c.b, 255);
193 assert_eq!(c.a, 64); }
195
196 #[test]
197 fn fuera_de_rango_clampea() {
198 let c = to_peniko(Color::rgba(1.5, -0.2, 0.5, 1.0)).to_rgba8();
199 assert_eq!((c.r, c.g, c.b), (255, 0, 128));
200 }
201}