1use crate::dcc_lsystem::LSystem;
2use crate::image::{draw_line_mut, fill_mut};
3use crate::renderer::{Renderer, TurtleRenderer};
4use crate::turtle::TurtleContainer;
5use gifski::progress::{NoProgress, ProgressReporter};
6use gifski::{CatResult, Collector, Repeat};
7use image::{ImageBuffer, Rgb};
8use pbr::ProgressBar;
9use std::fs::File;
10use std::io::Stdout;
11use std::path::PathBuf;
12use std::thread;
13use std::time::Duration;
14
15pub struct ImageRendererOptionsBuilder {
16 padding: Option<u32>,
17 thickness: Option<f32>,
18 fill_color: Option<Rgb<u8>>,
19 line_color: Option<Rgb<u8>>,
20}
21
22impl ImageRendererOptionsBuilder {
23 pub fn new() -> Self {
24 Self {
27 padding: None,
28 thickness: None,
29 fill_color: None,
30 line_color: None,
31 }
32 }
33
34 pub fn padding(&mut self, padding: u32) -> &mut Self {
35 self.padding = Some(padding);
36 self
37 }
38
39 pub fn thickness(&mut self, thickness: f32) -> &mut Self {
40 self.thickness = Some(thickness);
41 self
42 }
43
44 pub fn fill_color(&mut self, fill_color: Rgb<u8>) -> &mut Self {
45 self.fill_color = Some(fill_color);
46 self
47 }
48
49 pub fn line_color(&mut self, line_color: Rgb<u8>) -> &mut Self {
50 self.line_color = Some(line_color);
51 self
52 }
53
54 pub fn build(&mut self) -> ImageRendererOptions {
55 ImageRendererOptions {
56 padding: self.padding.unwrap(),
57 thickness: self.thickness.unwrap(),
58 fill_color: self.fill_color.unwrap(),
59 line_color: self.line_color.unwrap(),
60 }
61 }
62}
63
64impl Default for ImageRendererOptionsBuilder {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70pub struct ImageRendererOptions {
71 padding: u32,
72 thickness: f32,
73 fill_color: Rgb<u8>,
74 line_color: Rgb<u8>,
75}
76
77impl ImageRendererOptions {
78 pub fn padding(&self) -> u32 {
79 self.padding
80 }
81
82 pub fn thickness(&self) -> f32 {
83 self.thickness
84 }
85
86 pub fn fill_color(&self) -> Rgb<u8> {
87 self.fill_color
88 }
89
90 pub fn line_color(&self) -> Rgb<u8> {
91 self.line_color
92 }
93}
94
95pub struct VideoRendererOptionsBuilder {
96 filename: Option<String>,
97 fps: Option<usize>,
98 skip_by: Option<usize>,
99 padding: Option<u32>,
100 thickness: Option<f32>,
101 fill_color: Option<Rgb<u8>>,
102 line_color: Option<Rgb<u8>>,
103 progress_bar: Option<bool>,
104}
105
106impl VideoRendererOptionsBuilder {
107 pub fn new() -> Self {
108 Self {
111 filename: Some(String::from("render.gif")),
112 fps: Some(20),
113 skip_by: Some(0),
114 padding: None,
115 thickness: None,
116 fill_color: None,
117 line_color: None,
118 progress_bar: Some(false),
119 }
120 }
121
122 pub fn filename<T: Into<String>>(&mut self, filename: T) -> &mut Self {
123 self.filename = Some(filename.into());
124 self
125 }
126
127 pub fn fps(&mut self, fps: usize) -> &mut Self {
128 self.fps = Some(fps);
129 self
130 }
131
132 pub fn skip_by(&mut self, skip_by: usize) -> &mut Self {
133 self.skip_by = Some(skip_by);
134 self
135 }
136
137 pub fn padding(&mut self, padding: u32) -> &mut Self {
138 self.padding = Some(padding);
139 self
140 }
141
142 pub fn thickness(&mut self, thickness: f32) -> &mut Self {
143 self.thickness = Some(thickness);
144 self
145 }
146
147 pub fn fill_color(&mut self, fill_color: Rgb<u8>) -> &mut Self {
148 self.fill_color = Some(fill_color);
149 self
150 }
151
152 pub fn line_color(&mut self, line_color: Rgb<u8>) -> &mut Self {
153 self.line_color = Some(line_color);
154 self
155 }
156
157 pub fn progress_bar(&mut self, progress_bar: bool) -> &mut Self {
158 self.progress_bar = Some(progress_bar);
159 self
160 }
161
162 pub fn build(&mut self) -> VideoRendererOptions {
163 VideoRendererOptions {
164 filename: self.filename.as_ref().unwrap().clone(),
165 fps: self.fps.unwrap(),
166 skip_by: self.skip_by.unwrap(),
167 padding: self.padding.unwrap(),
168 thickness: self.thickness.unwrap(),
169 fill_color: self.fill_color.unwrap(),
170 line_color: self.line_color.unwrap(),
171 progress_bar: self.progress_bar.unwrap(),
172 }
173 }
174}
175
176impl Default for VideoRendererOptionsBuilder {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182pub struct VideoRendererOptions {
183 filename: String,
184 fps: usize,
185 skip_by: usize,
186 padding: u32,
187 thickness: f32,
188 fill_color: Rgb<u8>,
189 line_color: Rgb<u8>,
190 progress_bar: bool,
191}
192
193impl VideoRendererOptions {
194 pub fn filename(&self) -> &String {
195 &self.filename
196 }
197
198 pub fn fps(&self) -> usize {
199 self.fps
200 }
201
202 pub fn skip_by(&self) -> usize {
203 self.skip_by
204 }
205
206 pub fn padding(&self) -> u32 {
207 self.padding
208 }
209
210 pub fn thickness(&self) -> f32 {
211 self.thickness
212 }
213
214 pub fn fill_color(&self) -> Rgb<u8> {
215 self.fill_color
216 }
217
218 pub fn line_color(&self) -> Rgb<u8> {
219 self.line_color
220 }
221
222 pub fn progress_bar(&self) -> bool {
223 self.progress_bar
224 }
225}
226
227struct Lodecoder {
228 frames: Vec<PathBuf>,
229 fps: usize,
230}
231
232impl Lodecoder {
233 pub fn new(frames: Vec<PathBuf>, fps: usize) -> Self {
234 Self { frames, fps }
235 }
236
237 fn total_frames(&self) -> u64 {
238 self.frames.len() as u64
239 }
240
241 fn collect(&mut self, mut dest: Collector) -> CatResult<()> {
242 for (i, frame) in self.frames.drain(..).enumerate() {
243 let delay = ((i + 1) * 100 / self.fps) - (i * 100 / self.fps); dest.add_frame_png_file(i, frame, delay as f64)?;
245 }
246 Ok(())
247 }
248}
249
250impl<Q: TurtleContainer> Renderer<VideoRendererOptions> for TurtleRenderer<Q> {
251 type Output = ();
252
253 fn render(mut self, system: &LSystem, options: &VideoRendererOptions) -> Self::Output {
255 self.compute(system.get_state());
257
258 let (turtle_width, turtle_height, min_x, min_y) = self.state.inner().inner().bounds();
259
260 let width = 2 * options.padding + turtle_width;
263 let height = 2 * options.padding + turtle_height;
264
265 let mut buffer = ImageBuffer::new(width, height);
266 fill_mut(&mut buffer, options.fill_color);
267
268 let mut files = Vec::new();
269
270 let xp = |x: i32| -> u32 { (x - min_x + options.padding as i32) as u32 };
274
275 let yp = |y: i32| -> u32 {
276 (i64::from(height) - i64::from(y - min_y + options.padding as i32)) as u32
277 };
278
279 let mut absolute_frame_counter = 0;
280 let total_frame_counter = self.state.inner().inner().lines().len();
281
282 let mut pb = if options.progress_bar {
283 Some(ProgressBar::new(total_frame_counter as u64))
284 } else {
285 None
286 };
287
288 let dir = tempfile::tempdir().unwrap();
289 let mut workers = Vec::new();
290
291 for (frame_counter, (x1, y1, x2, y2)) in
292 self.state.inner().inner().lines().iter().enumerate()
293 {
294 draw_line_mut(
295 &mut buffer,
296 xp(*x1),
297 yp(*y1),
298 xp(*x2),
299 yp(*y2),
300 options.thickness,
301 options.line_color,
302 );
303
304 if options.progress_bar {
305 pb.as_mut().unwrap().inc();
306 }
307
308 if options.skip_by == 0 || frame_counter % options.skip_by == 0 {
309 let filename = dir
312 .path()
313 .join(format!("frame-{:08}.png", absolute_frame_counter));
314 absolute_frame_counter += 1;
315 files.push(filename.clone());
316
317 let local_buffer = buffer.clone();
318
319 workers.push(std::thread::spawn(move || {
321 local_buffer.save(filename).unwrap();
322 }));
323 }
324 }
325
326 for child in workers {
327 child.join().expect("Failure");
328 if options.progress_bar {
329 pb.as_mut().unwrap().inc();
330 }
331 }
332
333 if options.progress_bar {
334 pb.unwrap().finish();
335 }
336
337 let settings = gifski::Settings {
338 width: None,
339 height: None,
340 quality: 100,
341 fast: false,
342 repeat: Repeat::Infinite,
343 };
344
345 let mut decoder = Box::new(Lodecoder::new(files, options.fps));
346
347 let mut progress: Box<dyn ProgressReporter> = if !options.progress_bar {
348 Box::new(NoProgress {})
349 } else {
350 let mut pb: ProgressBar<Stdout> = ProgressBar::new(decoder.total_frames());
351 pb.set_max_refresh_rate(Some(Duration::from_millis(250)));
352 Box::new(pb)
353 };
354
355 let (collector, writer) = gifski::new(settings).expect("Failed to initialise gifski");
356 let decode_thread = thread::spawn(move || decoder.collect(collector));
357
358 let file = File::create(&options.filename).expect("Couldn't open output file");
359 writer.write(file, &mut *progress).expect("Failed to write");
360 let _ = decode_thread.join().expect("Failed to decode");
361 progress.done(&format!("Output written to {}", options.filename));
362
363 drop(dir);
365 }
366}
367
368impl<Q: TurtleContainer> Renderer<ImageRendererOptions> for TurtleRenderer<Q> {
369 type Output = ImageBuffer<Rgb<u8>, Vec<u8>>;
370
371 fn render(mut self, system: &LSystem, options: &ImageRendererOptions) -> Self::Output {
372 self.compute(system.get_state());
374
375 let (turtle_width, turtle_height, min_x, min_y) = self.state.inner().inner().bounds();
376 let width = 2 * options.padding + turtle_width;
377 let height = 2 * options.padding + turtle_height;
378
379 let mut buffer = ImageBuffer::new(width, height);
380 fill_mut(&mut buffer, options.fill_color);
381
382 let xp = |x: i32| -> u32 { (x - min_x + options.padding as i32) as u32 };
386
387 let yp = |y: i32| -> u32 {
388 (i64::from(height) - i64::from(y - min_y + options.padding as i32)) as u32
389 };
390
391 for (x1, y1, x2, y2) in self.state.inner().inner().lines() {
392 draw_line_mut(
393 &mut buffer,
394 xp(*x1),
395 yp(*y1),
396 xp(*x2),
397 yp(*y2),
398 options.thickness,
399 options.line_color,
400 );
401 }
402
403 buffer
404 }
405}