From a10617fe6d8bd7e18bc14ae865f5e5e1ac116156 Mon Sep 17 00:00:00 2001 From: Valentin Weber Date: Thu, 10 Jul 2025 17:55:47 +0200 Subject: [PATCH 1/2] feat: add (non-animated) image support --- Cargo.toml | 1 + src/main.rs | 32 +++++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) 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..4a11220 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,11 +4,6 @@ use std::{fs, 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,13 @@ use embedded_graphics::{ Drawable, Pixel, }; use serde::Deserialize; +use image::{imageops::FilterType, ImageReader}; + +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 +94,7 @@ 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 }, } fn main() -> Result<()> { @@ -224,6 +225,23 @@ 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)?; + } + } + } + } } } From 58ea934c9fb9ac687dc83d8416cebbdb39be1fe2 Mon Sep 17 00:00:00 2001 From: Valentin Weber Date: Thu, 10 Jul 2025 17:57:06 +0200 Subject: [PATCH 2/2] feat: Add animated GIF support --- src/main.rs | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4a11220..c4bed8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #![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 base64::Engine; @@ -15,7 +15,9 @@ use embedded_graphics::{ Drawable, Pixel, }; use serde::Deserialize; -use image::{imageops::FilterType, ImageReader}; +use image::{ + codecs::gif::GifDecoder, imageops::FilterType, AnimationDecoder, ImageReader, Pixel as iPixel, +}; use badgemagic::{ ble::Device as BleDevice, @@ -95,6 +97,7 @@ enum Content { BitmapBase64 { width: u32, bitmap_base64: String }, BitmapFile { width: u32, bitmap_file: PathBuf }, ImageFile { img_file: PathBuf }, + GifFile { gif_file: PathBuf }, } fn main() -> Result<()> { @@ -104,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) } @@ -130,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:?}"))?; @@ -242,6 +245,36 @@ fn gnerate_payload(args: &mut Args) -> Result { } } } + 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)?; + } + } + } + } + } } }