1use std::any::Any;
4
5use mkgraphic::prelude::*;
6use mkgraphic::support::canvas::Canvas;
7use num::ToPrimitive;
8
9use super::clef::ClefElement;
10use super::config::RenderConfig;
11use super::staff::StaffElement;
12use super::{STAFF_HEIGHT, STAFF_SPACE};
13use crate::notation::Clef;
14use crate::stream::Score;
15
16pub struct ScoreElement {
18 parts_data: Vec<PartData>,
20 #[allow(dead_code)]
22 config: RenderConfig,
23 width: f32,
25 height: f32,
27}
28
29struct PartData {
31 #[allow(dead_code)]
33 name: String,
34 clef: Clef,
36 measure_count: usize,
38 measures: Vec<MeasureData>,
40}
41
42struct MeasureData {
44 notes: Vec<(f64, u8)>,
46}
47
48impl ScoreElement {
49 pub fn new(score: &Score, config: RenderConfig) -> Self {
51 let mut parts_data = Vec::new();
52
53 for part in score.parts() {
54 let clef = Clef::treble();
56
57 let measures: Vec<MeasureData> = part
58 .measures()
59 .iter()
60 .map(|measure| {
61 let notes: Vec<(f64, u8)> = measure
62 .elements()
63 .iter()
64 .filter_map(|(offset, elem)| {
65 use crate::stream::MusicElement;
66 match elem {
67 MusicElement::Note(n) => {
68 Some((offset.to_f64().unwrap_or(0.0), n.midi()))
69 }
70 _ => None,
71 }
72 })
73 .collect();
74 MeasureData { notes }
75 })
76 .collect();
77
78 parts_data.push(PartData {
79 name: part.name().unwrap_or("Part").to_string(),
80 clef,
81 measure_count: part.measures().len(),
82 measures,
83 });
84 }
85
86 let num_parts = parts_data.len().max(1);
88 let num_measures = parts_data
89 .first()
90 .map(|p| p.measure_count)
91 .unwrap_or(0)
92 .max(1);
93
94 let width = config.margin_left
95 + config.clef_width
96 + config.key_sig_width
97 + config.time_sig_width
98 + (num_measures as f32 * config.measure_width)
99 + config.margin_right;
100
101 let staff_with_spacing = config.staff.height + config.staff_spacing;
102 let height =
103 config.margin_top + (num_parts as f32 * staff_with_spacing) + config.margin_bottom;
104
105 Self {
106 parts_data,
107 config,
108 width,
109 height,
110 }
111 }
112
113 pub fn width(&self) -> f32 {
115 self.width
116 }
117
118 pub fn height(&self) -> f32 {
120 self.height
121 }
122
123 pub fn draw_to_canvas(&self, canvas: &mut Canvas, config: &RenderConfig) {
125 let bg = &config.colors.background;
127 canvas.clear(Color::new(bg.0, bg.1, bg.2, bg.3));
128
129 let staff_with_spacing = config.staff.height + config.staff_spacing;
130
131 for (part_idx, part) in self.parts_data.iter().enumerate() {
133 let staff_y =
134 config.margin_top + STAFF_HEIGHT / 2.0 + (part_idx as f32 * staff_with_spacing);
135
136 let staff_width = self.width - config.margin_left - config.margin_right;
138 let mut staff = StaffElement::new(staff_width);
139 staff.set_position(config.margin_left, staff_y);
140 staff.draw_to_canvas(canvas, &config.colors.staff_lines);
141
142 let mut clef_element = ClefElement::new(part.clef);
144 clef_element.set_position(config.margin_left + 5.0, staff_y);
145 clef_element.draw_to_canvas(canvas, config);
146
147 let measure_start_x = config.margin_left
149 + config.clef_width
150 + config.key_sig_width
151 + config.time_sig_width;
152
153 for (measure_idx, measure_data) in part.measures.iter().enumerate() {
154 let measure_x = measure_start_x + (measure_idx as f32 * config.measure_width);
155 let is_last = measure_idx == part.measure_count - 1;
156
157 for (offset, midi) in &measure_data.notes {
159 let note_x = measure_x + (*offset as f32 * config.measure_width * 0.8);
160 let position = super::midi_to_staff_position(*midi, &part.clef);
161 let note_y = staff_y + position.to_y(STAFF_SPACE);
162
163 self.draw_simple_note(canvas, note_x, note_y, config);
165
166 if position.position > 4 || position.position < -4 {
168 staff.draw_ledger_lines(
169 canvas,
170 position.position,
171 note_x,
172 config.note.head_width,
173 &config.colors.staff_lines,
174 );
175 }
176 }
177
178 let bar_x = measure_x + config.measure_width;
180 let top_y = staff_y - STAFF_HEIGHT / 2.0;
181 let bottom_y = staff_y + STAFF_HEIGHT / 2.0;
182
183 if is_last {
184 super::staff::draw_double_bar_line(
185 canvas,
186 bar_x - 6.0,
187 top_y,
188 bottom_y,
189 (1.0, 3.0, 4.0),
190 &config.colors.bar_lines,
191 );
192 } else {
193 super::staff::draw_bar_line(
194 canvas,
195 bar_x,
196 top_y,
197 bottom_y,
198 1.0,
199 &config.colors.bar_lines,
200 );
201 }
202
203 if config.show_bar_numbers && measure_idx == 0 {
205 }
207 }
208
209 let initial_bar_x = measure_start_x;
211 let top_y = staff_y - STAFF_HEIGHT / 2.0;
212 let bottom_y = staff_y + STAFF_HEIGHT / 2.0;
213 super::staff::draw_bar_line(
214 canvas,
215 initial_bar_x,
216 top_y,
217 bottom_y,
218 1.0,
219 &config.colors.bar_lines,
220 );
221 }
222 }
223
224 fn draw_simple_note(&self, canvas: &mut Canvas, x: f32, y: f32, config: &RenderConfig) {
226 let colors = &config.colors.notes;
227 let color = Color::new(colors.0, colors.1, colors.2, colors.3);
228
229 canvas.fill_style(color);
230 canvas.begin_path();
231 canvas.add_circle(mkgraphic::support::circle::Circle::new(
232 Point::new(x + config.note.head_width / 2.0, y),
233 config.note.head_height / 2.0 * 0.9,
234 ));
235 canvas.fill();
236
237 canvas.stroke_style(color);
239 canvas.line_width(config.note.stem_width);
240 canvas.begin_path();
241 let stem_x = x + config.note.head_width;
242 canvas.move_to(Point::new(stem_x, y));
243 canvas.line_to(Point::new(stem_x, y - config.note.stem_height));
244 canvas.stroke();
245 }
246}
247
248impl Element for ScoreElement {
249 fn limits(&self, _ctx: &BasicContext) -> ViewLimits {
250 ViewLimits::fixed(self.width, self.height)
251 }
252
253 fn draw(&self, _ctx: &Context) {
254 }
257
258 fn as_any(&self) -> &dyn Any {
259 self
260 }
261
262 fn as_any_mut(&mut self) -> &mut dyn Any {
263 self
264 }
265}
266
267pub fn render_score_to_image(score: &Score, config: &RenderConfig) -> Option<Vec<u8>> {
269 let element = ScoreElement::new(score, config.clone());
270 let width = (element.width() * config.scale) as u32;
271 let height = (element.height() * config.scale) as u32;
272
273 let mut canvas = Canvas::new(width, height)?;
274
275 if config.scale != 1.0 {
276 canvas.scale(config.scale, config.scale);
277 }
278
279 element.draw_to_canvas(&mut canvas, config);
280
281 canvas.pixmap().encode_png().ok()
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_score_element_creation() {
291 let score = Score::new();
292 let config = RenderConfig::default();
293 let element = ScoreElement::new(&score, config);
294
295 assert!(element.width() > 0.0);
296 assert!(element.height() > 0.0);
297 }
298}