Add animated GIF output with parallelised frame quantization
Render an animated clct_animation.gif alongside the per-step PNGs. Frames are loaded and colour-quantized in parallel via rayon, then written sequentially with the gif crate. Also converts timestamps to CET/CEST using chrono-tz. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
46
src/main.rs
46
src/main.rs
@@ -139,14 +139,15 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
let forecast_dt = dt + chrono::Duration::hours(step as i64);
|
||||
let forecast_dt = forecast_dt.with_timezone(&chrono_tz::Europe::Berlin);
|
||||
let title = if step == 0 {
|
||||
format!(
|
||||
"Conditions at {} UTC",
|
||||
"Conditions at {} CET",
|
||||
forecast_dt.format("%Y-%m-%d %H:%M")
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Prediction at {} UTC (+{:02}h)",
|
||||
"Prediction at {} CET (+{:02}h)",
|
||||
forecast_dt.format("%Y-%m-%d %H:%M"),
|
||||
step
|
||||
)
|
||||
@@ -167,6 +168,47 @@ fn main() -> Result<()> {
|
||||
eprintln!(" {}", p.display());
|
||||
}
|
||||
|
||||
if output_paths.len() > 1 {
|
||||
eprintln!("Building animation...");
|
||||
let gif_path = cache_dir.join("clct_animation.gif");
|
||||
create_gif(&output_paths, &gif_path).context("Creating GIF")?;
|
||||
eprintln!(" GIF: {}", gif_path.display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_gif(png_paths: &[PathBuf], out_path: &PathBuf) -> Result<()> {
|
||||
use gif::{Encoder, Frame, Repeat};
|
||||
|
||||
// Load PNGs and quantize to 256-colour palettes in parallel (the bottleneck).
|
||||
let n = png_paths.len();
|
||||
let frames: Vec<Frame<'static>> = png_paths
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.map(|(i, path)| -> Result<Frame<'static>> {
|
||||
let img = image::open(path)
|
||||
.with_context(|| format!("Loading PNG {}", path.display()))?
|
||||
.into_rgba8();
|
||||
let (width, height) = img.dimensions();
|
||||
let mut pixels = img.into_raw();
|
||||
// speed 8: slightly better quality than the default (10)
|
||||
let mut frame =
|
||||
Frame::from_rgba_speed(width as u16, height as u16, &mut pixels, 8);
|
||||
// Pause longer on first and last frame; 1 unit = 10 ms
|
||||
frame.delay = if i == 0 || i == n - 1 { 200 } else { 40 };
|
||||
Ok(frame)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
// Write frames sequentially (GIF encoder is not thread-safe).
|
||||
let file = std::fs::File::create(out_path)?;
|
||||
let (w, h) = (frames[0].width, frames[0].height);
|
||||
let mut encoder = Encoder::new(file, w, h, &[])?;
|
||||
encoder.set_repeat(Repeat::Infinite)?;
|
||||
for frame in &frames {
|
||||
encoder.write_frame(frame)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user