mirror of
https://github.com/fossasia/badgemagic-rs
synced 2025-07-26 21:53:57 +00:00
Merge 159c678e15
into 5d745ab8fd
This commit is contained in:
commit
d213d81de2
3 changed files with 862 additions and 24 deletions
793
Cargo.lock
generated
793
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||
|
|
92
src/main.rs
92
src/main.rs
|
@ -1,14 +1,10 @@
|
|||
#![warn(clippy::all, clippy::pedantic)]
|
||||
#![allow(clippy::unnecessary_debug_formatting)]
|
||||
#![allow(clippy::too_many_lines)]
|
||||
|
||||
use std::{fs, path::PathBuf};
|
||||
use std::{fs, 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::{
|
||||
|
@ -19,8 +15,18 @@ use embedded_graphics::{
|
|||
text::Text,
|
||||
Drawable, Pixel,
|
||||
};
|
||||
use image::{
|
||||
codecs::gif::GifDecoder, imageops::FilterType, AnimationDecoder, ImageReader,
|
||||
Pixel as ImagePixel,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
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
|
||||
#[clap(
|
||||
|
@ -92,8 +98,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 +109,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 +135,10 @@ fn list_devices(transport: &TransportProtocol) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn gnerate_payload(args: &mut Args) -> Result<PayloadBuffer> {
|
||||
fn generate_payload(args: &mut Args) -> Result<PayloadBuffer> {
|
||||
const DISPLAY_HEIGHT: u32 = 11;
|
||||
const DISPLAY_WIDTH: u32 = 44;
|
||||
|
||||
let config_path = args.config.take().unwrap_or_default();
|
||||
let config = fs::read_to_string(&config_path)
|
||||
.with_context(|| format!("load config: {config_path:?}"))?;
|
||||
|
@ -171,8 +180,9 @@ fn gnerate_payload(args: &mut Args) -> Result<PayloadBuffer> {
|
|||
let lines: Vec<_> = bitstring.trim().lines().collect();
|
||||
|
||||
anyhow::ensure!(
|
||||
lines.len() == 11,
|
||||
"expected 11 lines in bitstring, found {} lines",
|
||||
lines.len() == DISPLAY_HEIGHT as usize,
|
||||
"expected {} lines in bitstring, found {} lines",
|
||||
DISPLAY_HEIGHT,
|
||||
lines.len()
|
||||
);
|
||||
let width = lines[0].len();
|
||||
|
@ -224,6 +234,64 @@ fn gnerate_payload(args: &mut Args) -> Result<PayloadBuffer> {
|
|||
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, DISPLAY_HEIGHT, FilterType::Nearest)
|
||||
.into_luma8();
|
||||
let (width, height) = img.dimensions();
|
||||
let mut buffer = payload.add_message(style, width.div_ceil(8) as usize);
|
||||
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(fs::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 != DISPLAY_HEIGHT || width != DISPLAY_WIDTH {
|
||||
anyhow::bail!(
|
||||
"Expected {}x{} pixel gif file",
|
||||
DISPLAY_WIDTH,
|
||||
DISPLAY_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
let mut buffer = payload.add_message(
|
||||
style,
|
||||
((DISPLAY_WIDTH as usize + 4) * frame_count).div_ceil(8),
|
||||
);
|
||||
|
||||
for (i, frame) in frames.iter().enumerate() {
|
||||
let buf = frame.buffer();
|
||||
for y in 0..DISPLAY_HEIGHT {
|
||||
for x in 0..DISPLAY_WIDTH {
|
||||
if buf.get_pixel(x, y).to_luma().0 > [31] {
|
||||
Pixel(
|
||||
Point::new(
|
||||
(x as usize + i * (DISPLAY_WIDTH as usize + 4))
|
||||
.try_into()?,
|
||||
y.try_into()?,
|
||||
),
|
||||
BinaryColor::On,
|
||||
)
|
||||
.draw(&mut buffer)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue