ledcat 0.2.0

Control lots of LED's over lots of protocols
use crate::device::*;
use std::io;

pub enum Format {
    RGB24,
    RGB16,
    RGB12,
    RGB8,
    GS1,
}

pub struct Generic {
    pub format: Format,
}

impl Device for Generic {
    fn write_frame(&self, writer: &mut dyn io::Write, pixels: &[Pixel]) -> io::Result<()> {
        match self.format {
            Format::RGB24 => {
                let buf: Vec<u8> = pixels
                    .iter()
                    .flat_map(|pix| vec![pix.r, pix.g, pix.b])
                    .collect();
                writer.write_all(&buf)?;
            }
            Format::RGB16 => {
                let buf: Vec<u8> = pixels
                    .iter()
                    .flat_map(|pix| {
                        vec![
                            (pix.r & 0xf8) | (pix.g >> 5),
                            (pix.g & 0x08) << 5 | (pix.b >> 3),
                        ]
                    })
                    .collect();
                writer.write_all(&buf)?;
            }
            Format::RGB12 => {
                let buf: Vec<u8> = pixels
                    .chunks(2)
                    .flat_map(|ch| {
                        let (a, b) = (&ch[0], ch.get(1).cloned().unwrap_or_default());
                        vec![
                            (a.r & 0xf0) | (a.g >> 4),
                            (a.b & 0xf0) | (b.r >> 4),
                            (b.g & 0xf0) | (b.b >> 4),
                        ]
                    })
                    .collect();
                writer.write_all(&buf)?;
            }
            Format::RGB8 => {
                let buf: Vec<u8> = pixels
                    .iter()
                    .map(|p| (p.b & 0xc0) | ((p.g >> 2) & 0x3c) | (p.r & 0x3))
                    .collect();
                writer.write_all(&buf)?;
            }
            Format::GS1 => {
                assert!(pixels.len() % 8 == 0);
                let prebuf: Vec<u8> = pixels
                    .iter()
                    .map(|p| if grayscale(*p) > 127 { 1 } else { 0 })
                    .collect();
                let packed: Vec<u8> = prebuf
                    .chunks(8)
                    .map(|chunk| {
                        chunk
                            .iter()
                            .enumerate()
                            .fold(0, |pack, (i, b)| pack | b << i)
                    })
                    .collect();
                writer.write_all(&packed)?;
            }
        }
        Ok(())
    }
}

pub fn command<'a, 'b>() -> clap::App<'a, 'b> {
    clap::SubCommand::with_name("generic")
        .about("Output data as RGB24 or another pixel format")
        .arg(
            clap::Arg::with_name("format")
                .short("f")
                .long("format")
                .takes_value(true)
                .default_value("rgb24")
                .possible_values(&["rgb24", "rgb16", "rgb12", "rgb8", "gs1"]),
        )
}

pub fn from_command(args: &clap::ArgMatches, _: &GlobalArgs) -> io::Result<FromCommand> {
    let format = match args.value_of("format").unwrap() {
        "rgb16" => Format::RGB16,
        "rgb12" => Format::RGB12,
        "rgb8" => Format::RGB8,
        "gs1" => Format::GS1,
        _ => Format::RGB24,
    };
    Ok(FromCommand::Device(Box::new(Generic { format })))
}

fn grayscale(p: Pixel) -> u8 {
    let g = (0.2125 * p.r as f32) + (0.7154 * p.g as f32) + (0.0721 * p.b as f32);
    g.round() as u8
}