feat: Add animated GIF support

This commit is contained in:
Valentin Weber 2025-07-10 17:57:06 +02:00
parent a10617fe6d
commit 58ea934c9f
No known key found for this signature in database
GPG key ID: 44797000F143F522

View file

@ -1,7 +1,7 @@
#![warn(clippy::all, clippy::pedantic)] #![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::unnecessary_debug_formatting)] #![allow(clippy::unnecessary_debug_formatting)]
use std::{fs, path::PathBuf}; use std::{fs, fs::File, io::BufReader, path::PathBuf};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use base64::Engine; use base64::Engine;
@ -15,7 +15,9 @@ use embedded_graphics::{
Drawable, Pixel, Drawable, Pixel,
}; };
use serde::Deserialize; use serde::Deserialize;
use image::{imageops::FilterType, ImageReader}; use image::{
codecs::gif::GifDecoder, imageops::FilterType, AnimationDecoder, ImageReader, Pixel as iPixel,
};
use badgemagic::{ use badgemagic::{
ble::Device as BleDevice, ble::Device as BleDevice,
@ -95,6 +97,7 @@ enum Content {
BitmapBase64 { width: u32, bitmap_base64: String }, BitmapBase64 { width: u32, bitmap_base64: String },
BitmapFile { width: u32, bitmap_file: PathBuf }, BitmapFile { width: u32, bitmap_file: PathBuf },
ImageFile { img_file: PathBuf }, ImageFile { img_file: PathBuf },
GifFile { gif_file: PathBuf },
} }
fn main() -> Result<()> { fn main() -> Result<()> {
@ -104,7 +107,7 @@ fn main() -> Result<()> {
return list_devices(&args.transport); return list_devices(&args.transport);
} }
let payload = gnerate_payload(&mut args)?; let payload = generate_payload(&mut args)?;
write_payload(&args.transport, payload) write_payload(&args.transport, payload)
} }
@ -130,7 +133,7 @@ fn list_devices(transport: &TransportProtocol) -> Result<()> {
Ok(()) Ok(())
} }
fn gnerate_payload(args: &mut Args) -> Result<PayloadBuffer> { fn generate_payload(args: &mut Args) -> Result<PayloadBuffer> {
let config_path = args.config.take().unwrap_or_default(); let config_path = args.config.take().unwrap_or_default();
let config = fs::read_to_string(&config_path) let config = fs::read_to_string(&config_path)
.with_context(|| format!("load config: {config_path:?}"))?; .with_context(|| format!("load config: {config_path:?}"))?;
@ -242,6 +245,36 @@ fn gnerate_payload(args: &mut Args) -> Result<PayloadBuffer> {
} }
} }
} }
Content::GifFile { gif_file } => {
let file_in = BufReader::new(File::open(gif_file)?);
let frames = GifDecoder::new(file_in)?
.into_frames()
.collect_frames()
.expect("error decoding gif");
let frame_count = frames.len();
let (width, height) = frames.first().unwrap().buffer().dimensions();
if height != 11 || width != 44 {
anyhow::bail!("Expected 44x11 pixel gif file");
}
let mut buffer = payload.add_message(style, (48 * frame_count + 7) / 8);
for (i, frame) in frames.iter().enumerate() {
let buf = frame.buffer();
for y in 0..11 {
for x in 0..44 {
if buf.get_pixel(x, y).to_luma().0 > [31] {
Pixel(
Point::new((x as usize + i * 48).try_into()?, y.try_into()?),
BinaryColor::On,
)
.draw(&mut buffer)?;
}
}
}
}
}
} }
} }