From 366b06ab94eb4406725918c22e1dadcbd16e0140 Mon Sep 17 00:00:00 2001
From: Stephen D <webmaster@scd31.com>
Date: Sat, 22 Feb 2025 16:13:45 -0500
Subject: [PATCH] gif exporting

---
 Cargo.lock  |  2 ++
 Cargo.toml  |  2 ++
 src/main.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++---------
 3 files changed, 62 insertions(+), 11 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index f84b200..be444f8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -787,10 +787,12 @@ dependencies = [
 name = "rapidriter-cat"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "bitvec",
  "clap",
  "colored",
  "console",
+ "gif",
  "image",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index 81bc4a9..f8b9715 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,10 +7,12 @@ edition = "2021"
 crate-type = ["cdylib", "rlib"]
 
 [dependencies]
+anyhow = "1.0.96"
 bitvec = "1.0.1"
 clap = { version = "4.5.30", features = ["derive"] }
 colored = "3.0.0"
 console = "0.15.10"
+gif = "0.13.1"
 
 [build-dependencies]
 image = "0.25.5"
diff --git a/src/main.rs b/src/main.rs
index b9174f5..af1e079 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,7 @@
 use std::{
-    io::{self, Read},
+    borrow::Cow,
+    fs::File,
+    path::PathBuf,
     thread,
     time::{Duration, Instant},
 };
@@ -9,6 +11,7 @@ use bitvec::{order::Msb0, view::AsBits};
 use clap::Parser;
 use colored::Colorize;
 use console::Term;
+use gif::{Encoder, Frame, Repeat};
 use rapidriter_cat::next_frame;
 
 const WIDTH: usize = 96;
@@ -16,17 +19,59 @@ const HEIGHT: usize = 38;
 
 mod arg;
 
-fn main() {
+fn main() -> anyhow::Result<()> {
     let args = Args::parse();
 
-    // clear screen
-    print!("\x1B[2J");
-
-    if args.interactive {
-        interactive_mode();
+    if let Some(path) = args.out {
+        generate_gif(path)?;
     } else {
-        non_interactive_mode();
+        // clear screen
+        print!("\x1B[2J");
+
+        if args.interactive {
+            interactive_mode();
+        } else {
+            non_interactive_mode();
+        }
     }
+
+    Ok(())
+}
+
+fn generate_gif(path: PathBuf) -> anyhow::Result<()> {
+    let mut gif = File::create(path)?;
+    let mut encoder = Encoder::new(
+        &mut gif,
+        WIDTH.try_into().unwrap(),
+        HEIGHT.try_into().unwrap(),
+        &[0, 0, 0, 0xFF, 0, 0],
+    )?;
+    encoder.set_repeat(Repeat::Infinite)?;
+
+    let mut frame = [0; 456];
+    for i in 0..100 {
+        let done = unsafe { next_frame(i, frame.as_mut_ptr()) } == 1;
+        let mut exploded_frame = vec![];
+        for b in frame.as_bits::<Msb0>() {
+            exploded_frame.push(if *b { 1 } else { 0 });
+        }
+
+        let f = Frame {
+            delay: 10, // 100 ms, or 10 FPS
+            width: WIDTH.try_into().unwrap(),
+            height: HEIGHT.try_into().unwrap(),
+            buffer: Cow::Borrowed(&*exploded_frame),
+
+            ..Default::default()
+        };
+        encoder.write_frame(&f)?;
+
+        if done {
+            break;
+        }
+    }
+
+    Ok(())
 }
 
 fn interactive_mode() {
@@ -74,12 +119,14 @@ fn non_interactive_mode() {
             thread::sleep(target - now);
         }
 
-        if unsafe { next_frame(i, frame.as_mut_ptr()) } == 1 {
-            break;
-        }
+        let done = unsafe { next_frame(i, frame.as_mut_ptr()) } == 1;
 
         draw_frame(&frame);
         println!("Frame: {}", i);
+
+        if done {
+            break;
+        }
     }
 }
 
-- 
GitLab