diff --git a/Cargo.toml b/Cargo.toml index 84c9310..af8a46a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,3 +50,4 @@ serde_json = { version = "1.0.140", optional = true } time = "0.3.41" toml = { version = "0.9.2", optional = true } zerocopy = { version = "0.8.26", features = ["derive"] } +image = "0.25.6" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 116a66c..c4bed8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,9 @@ #![warn(clippy::all, clippy::pedantic)] #![allow(clippy::unnecessary_debug_formatting)] -use std::{fs, path::PathBuf}; +use std::{fs, fs::File, io::BufReader, path::PathBuf}; use anyhow::{Context, Result}; -use badgemagic::{ - ble::Device as BleDevice, - protocol::{Mode, PayloadBuffer, Speed, Style}, - usb_hid::Device as UsbDevice, -}; use base64::Engine; use clap::{Parser, ValueEnum}; use embedded_graphics::{ @@ -20,6 +15,15 @@ use embedded_graphics::{ Drawable, Pixel, }; use serde::Deserialize; +use image::{ + codecs::gif::GifDecoder, imageops::FilterType, AnimationDecoder, ImageReader, Pixel as iPixel, +}; + +use badgemagic::{ + ble::Device as BleDevice, + protocol::{Mode, PayloadBuffer, Speed, Style}, + usb_hid::Device as UsbDevice, +}; #[derive(Parser)] /// Upload a configuration with up to 8 messages to an LED badge @@ -92,8 +96,8 @@ enum Content { Bitstring { bitstring: String }, BitmapBase64 { width: u32, bitmap_base64: String }, BitmapFile { width: u32, bitmap_file: PathBuf }, - // TODO: implement png - // PngFile { png_file: PathBuf }, + ImageFile { img_file: PathBuf }, + GifFile { gif_file: PathBuf }, } fn main() -> Result<()> { @@ -103,7 +107,7 @@ fn main() -> Result<()> { return list_devices(&args.transport); } - let payload = gnerate_payload(&mut args)?; + let payload = generate_payload(&mut args)?; write_payload(&args.transport, payload) } @@ -129,7 +133,7 @@ fn list_devices(transport: &TransportProtocol) -> Result<()> { Ok(()) } -fn gnerate_payload(args: &mut Args) -> Result { +fn generate_payload(args: &mut Args) -> Result { let config_path = args.config.take().unwrap_or_default(); let config = fs::read_to_string(&config_path) .with_context(|| format!("load config: {config_path:?}"))?; @@ -224,6 +228,53 @@ fn gnerate_payload(args: &mut Args) -> Result { let image = Image::new(&image_raw, Point::zero()); payload.add_message_drawable(style, &image); } + Content::ImageFile { img_file } => { + let img_reader = ImageReader::open(img_file)?; + let img = img_reader + .decode()? + .resize(u32::MAX, 11, FilterType::Nearest) + .into_luma8(); + let (width, height) = img.dimensions(); + let mut buffer = payload.add_message(style, (width as usize + 7) / 8); + for y in 0..height { + for x in 0..width { + if img.get_pixel(x, y).0 > [31] { + Pixel(Point::new(x.try_into()?, y.try_into()?), BinaryColor::On) + .draw(&mut buffer)?; + } + } + } + } + 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)?; + } + } + } + } + } } }