diff --git a/Cargo.toml b/Cargo.toml index 642552b..46cf337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ default = ["text", "binary"] all = ["text", "binary"] text = ["dep:atoi_radix10"] binary = [] +palette = [] [dev-dependencies] tempfile = "*" diff --git a/src/color.rs b/src/color.rs index 41582e5..fe784de 100644 --- a/src/color.rs +++ b/src/color.rs @@ -2,13 +2,23 @@ use std::fmt::Display; use rand::{distr::StandardUniform, prelude::Distribution}; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Color { RGB24(u8, u8, u8), RGBA32(u8, u8, u8, u8), W8(u8), } +impl Color { + pub fn to_bytes(&self) -> [u8; 4] { + match self { + Color::RGB24(r, g, b) => [*r, *g, *b, 0xff], + Color::RGBA32(r, g, b, a) => [*r, *g, *b, *a], + Color::W8(w) => [*w, *w, *w, 0xff], + } + } +} + impl Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/flutclient.rs b/src/flutclient.rs index 75f0bb2..3bbb1f5 100644 --- a/src/flutclient.rs +++ b/src/flutclient.rs @@ -5,11 +5,17 @@ use std::{ use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}; +#[cfg(feature = "binary")] +use crate::protocols::BinaryParser; +#[cfg(feature = "palette")] +use crate::protocols::PaletteParser; +#[cfg(feature = "text")] +use crate::protocols::TextParser; use crate::{ get_pixel, grid::{self, Flut}, increment_counter, - protocols::{BinaryParser, IOProtocol, Parser, Responder, TextParser}, + protocols::{IOProtocol, Parser, Responder}, set_pixel_rgba, Canvas, Color, Command, Coordinate, Protocol, ProtocolStatus, Response, }; @@ -73,6 +79,7 @@ macro_rules! build_parser_type_enum { build_parser_type_enum! { TextParser: TextParser: "text", BinaryParser: BinaryParser: "binary", + PaletteParser: PaletteParser: "palette", } pub struct FlutClient @@ -148,23 +155,45 @@ where match_parser!(parser: self.parser => parser.change_canvas(canvas)) } - fn change_protocol(&mut self, protocol: &Protocol) { + #[cfg(feature = "palette")] + fn change_color(&mut self, index: u8, color: Color) -> () { + match &mut self.parser { + ParserTypes::PaletteParser(ref mut parser) => parser.set_color(index, color), + _ => {} + }; + } + + async fn change_protocol(&mut self, protocol: &Protocol) -> io::Result<()> { match protocol { #[cfg(feature = "text")] Protocol::Text => self.parser = ParserTypes::TextParser(TextParser::default()), #[cfg(not(feature = "text"))] Protocol::Text => { - self.writer.write(b"feature \"text\" is not enabled."); - self.writer.flush(); + self.writer + .write_all(b"feature \"text\" is not enabled.") + .await?; + self.writer.flush().await?; } #[cfg(feature = "binary")] Protocol::Binary => self.parser = ParserTypes::BinaryParser(BinaryParser::default()), #[cfg(not(feature = "binary"))] Protocol::Binary => { - self.writer.write(b"feature \"binary\" is not enabled."); - self.writer.flush(); + self.writer + .write_all(b"feature \"binary\" is not enabled.") + .await?; + self.writer.flush().await?; + } + #[cfg(feature = "palette")] + Protocol::Palette => self.parser = ParserTypes::PaletteParser(PaletteParser::default()), + #[cfg(not(feature = "palette"))] + Protocol::Palette => { + self.writer + .write_all(b"feature \"binary\" is not enabled.") + .await?; + self.writer.flush().await?; } } + Ok(()) } pub fn new(reader: R, writer: W, grids: Arc<[grid::Flut]>) -> Self { @@ -193,9 +222,13 @@ where break 'outer; } Ok(Command::ChangeProtocol(protocol)) => { - self.change_protocol(&protocol); + self.change_protocol(&protocol).await?; break 'outer; } + #[cfg(feature = "palette")] + Ok(Command::ChangeColor(index, color)) => { + self.change_color(index, color); + } Err(err) if err.kind() == ErrorKind::UnexpectedEof => { tracing::error!("Process socket got error: {err:?}"); increment_counter(self.counter); diff --git a/src/lib.rs b/src/lib.rs index bb16ec3..01a45b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,7 @@ pub enum ProtocolStatus { pub enum Protocol { Text, Binary, + Palette, } #[derive(Debug, PartialEq)] @@ -75,6 +76,8 @@ pub enum Command { SetPixel(Canvas, Coordinate, Coordinate, Color), ChangeCanvas(Canvas), ChangeProtocol(Protocol), + #[cfg(feature = "palette")] + ChangeColor(u8, Color), } #[derive(Debug, PartialEq)] diff --git a/src/protocols.rs b/src/protocols.rs index 12aa1a6..5fe8c4c 100644 --- a/src/protocols.rs +++ b/src/protocols.rs @@ -1,9 +1,11 @@ mod binary_protocol; +mod palette_protocol; mod text_protocol; use std::io; pub use binary_protocol::BinaryParser; +pub use palette_protocol::PaletteParser; pub use text_protocol::TextParser; use tokio::io::AsyncWriteExt; diff --git a/src/protocols/palette_protocol.rs b/src/protocols/palette_protocol.rs new file mode 100644 index 0000000..2e3c543 --- /dev/null +++ b/src/protocols/palette_protocol.rs @@ -0,0 +1,181 @@ +use std::io::{self, Error, ErrorKind}; + +use image::EncodableLayout; +use rand::random; +use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}; + +use crate::{Canvas, Color, Command, Response}; + +use super::{IOProtocol, Parser, Responder}; + +const SIZE_BIN: u8 = 115; +const PROTOCOLS_BIN: u8 = 116; +const HELP_BIN: u8 = 104; +const GET_PX_BIN: u8 = 32; +const SET_PX_PALETTE_BIN: u8 = 33; +#[cfg(feature = "palette")] +const SET_PALETTE_COLOR: u8 = 34; + +#[derive(Clone)] +pub struct PaletteParser { + colors: [Color; 256], +} + +impl PaletteParser { + pub fn set_color(&mut self, index: u8, color: Color) { + self.colors[index as usize] = color; + } +} + +impl Default for PaletteParser { + fn default() -> Self { + PaletteParser { + colors: [0; 256].map(|_| random()), + } + } +} + +impl Parser for PaletteParser { + async fn parse(&self, reader: &mut R) -> io::Result { + let fst = reader.read_u8().await; + match fst { + Ok(command) => match command { + HELP_BIN => Ok(Command::Help), + PROTOCOLS_BIN => Ok(Command::Protocols), + SIZE_BIN => { + let canvas = reader.read_u8().await?; + Ok(Command::Size(canvas)) + } + GET_PX_BIN => { + let canvas = reader.read_u8().await?; + let horizontal = reader.read_u16().await?; + let vertical = reader.read_u16().await?; + Ok(Command::GetPixel(canvas, horizontal, vertical)) + } + SET_PX_PALETTE_BIN => { + let canvas = reader.read_u8().await?; + let horizontal = reader.read_u16().await?; + let vertical = reader.read_u16().await?; + let color = reader.read_u8().await?; + Ok(Command::SetPixel(canvas, horizontal, vertical, unsafe { + self.colors.get_unchecked(color as usize).clone() + })) + } + #[cfg(feature = "palette")] + SET_PALETTE_COLOR => { + let index = reader.read_u8().await?; + let r = reader.read_u8().await?; + let g = reader.read_u8().await?; + let b = reader.read_u8().await?; + Ok(Command::ChangeColor(index, Color::RGB24(r, g, b))) + } + _ => { + tracing::error!("received illegal command: {command}"); + Err(Error::from(ErrorKind::InvalidInput)) + } + }, + Err(err) => { + tracing::error!("{err}"); + Err(err) + } + } + } +} + +impl IOProtocol for PaletteParser { + fn change_canvas(&mut self, _canvas: Canvas) -> io::Result<()> { + Err(Error::from(ErrorKind::Unsupported)) + } +} + +impl Responder for PaletteParser { + async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()> { + match response { + Response::Help => { + writer + .write_all( + self.colors + .iter() + .map(|c| c.to_bytes()) + .collect::>() + .concat() + .as_bytes(), + ) + .await + } + Response::Protocols(protos) => { + for protocol in protos { + match protocol { + crate::ProtocolStatus::Enabled(proto) => { + writer + .write_all(format!("Enabled: {}\n", proto).as_bytes()) + .await?; + } + crate::ProtocolStatus::Disabled(proto) => { + writer + .write_all(format!("Disabled: {}\n", proto).as_bytes()) + .await?; + } + } + } + Ok(()) + } + Response::Size(x, y) => { + writer.write_u16(x).await?; + writer.write_u16(y).await + } + Response::GetPixel(_, _, c) => { + writer.write_u8(c[0]).await?; + writer.write_u8(c[1]).await?; + writer.write_u8(c[2]).await + } + } + } +} + +#[cfg(test)] +#[allow(clippy::needless_return)] +mod tests { + use super::*; + use tokio::io::BufReader; + + #[tokio::test] + async fn test_palette_px_set_parse() { + let parser = PaletteParser::default(); + let reader = tokio_test::io::Builder::new() + .read(&[SET_PX_PALETTE_BIN, 0x01, 0x69, 0x42, 0x42, 0x69, 0x82]) + .build(); + let mut bufreader = BufReader::new(reader); + let thingy = parser.parse(&mut bufreader).await.unwrap(); + assert_eq!( + thingy, + Command::SetPixel(1, 0x6942, 0x4269, parser.colors[0x82].clone()) + ); + } + + #[cfg(feature = "palette")] + #[tokio::test] + async fn test_palette_set_palette_color_parse() { + let parser = PaletteParser::default(); + let reader = tokio_test::io::Builder::new() + .read(&[SET_PALETTE_COLOR, 0x04, 0xfa, 0xbd, 0x2f]) + .build(); + let mut bufreader = BufReader::new(reader); + let thingy = parser.parse(&mut bufreader).await.unwrap(); + assert_eq!( + thingy, + Command::ChangeColor(0x04, Color::RGB24(0xfa, 0xbd, 0x2f)) + ); + } + + #[tokio::test] + async fn test_palette_set_color() { + let mut parser = PaletteParser::default(); + parser.set_color(0x04, Color::RGB24(0xfa, 0xbd, 0x2f)); + parser.set_color(0x13, Color::RGB24(0x23, 0x45, 0x67)); + parser.set_color(0x69, Color::RGB24(0xb0, 0x07, 0x1e)); + assert_eq!(parser.colors[0x04], Color::RGB24(0xfa, 0xbd, 0x2f)); + assert_eq!(parser.colors[0x13], Color::RGB24(0x23, 0x45, 0x67)); + assert_eq!(parser.colors[0x69], Color::RGB24(0xb0, 0x07, 0x1e)); + } +} diff --git a/src/protocols/text_protocol.rs b/src/protocols/text_protocol.rs index 7303014..fae7129 100644 --- a/src/protocols/text_protocol.rs +++ b/src/protocols/text_protocol.rs @@ -106,6 +106,7 @@ impl TextParser { match protocol { "binary" => Ok(Command::ChangeProtocol(Protocol::Binary)), "text" => Ok(Command::ChangeProtocol(Protocol::Text)), + "palette" => Ok(Command::ChangeProtocol(Protocol::Palette)), _ => Err(Error::from(ErrorKind::InvalidInput)), } }