From 755889e82d912c997e7d2ede7150832877e03ccf Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 17 Mar 2024 17:41:08 +0100 Subject: [PATCH] Add /cells --- Cargo.lock | 1 + showbits-thermal-printer/Cargo.toml | 1 + showbits-thermal-printer/src/drawer.rs | 3 + showbits-thermal-printer/src/drawer/cells.rs | 89 ++++++++++++++++++++ showbits-thermal-printer/src/printer.rs | 2 +- showbits-thermal-printer/src/server.rs | 19 +++++ 6 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 showbits-thermal-printer/src/drawer/cells.rs diff --git a/Cargo.lock b/Cargo.lock index fd687c7..0a3029b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1385,6 +1385,7 @@ dependencies = [ "image", "mime_guess", "palette", + "rand", "rust-embed", "serde", "showbits-common", diff --git a/showbits-thermal-printer/Cargo.toml b/showbits-thermal-printer/Cargo.toml index 889f46a..9f1147e 100644 --- a/showbits-thermal-printer/Cargo.toml +++ b/showbits-thermal-printer/Cargo.toml @@ -12,6 +12,7 @@ escpos = { version = "0.7.2", features = ["full"] } image.workspace = true mime_guess = "2.0.4" palette.workspace = true +rand = "0.8.5" rust-embed = "8.3.0" serde = { version = "1.0.197", features = ["derive"] } showbits-common.workspace = true diff --git a/showbits-thermal-printer/src/drawer.rs b/showbits-thermal-printer/src/drawer.rs index 1265eb3..e00308f 100644 --- a/showbits-thermal-printer/src/drawer.rs +++ b/showbits-thermal-printer/src/drawer.rs @@ -1,4 +1,5 @@ mod calendar; +mod cells; use image::{Luma, Pixel, RgbaImage}; use palette::{FromColor, IntoColor, LinLumaa}; @@ -24,6 +25,7 @@ pub enum Command { Photo { image: RgbaImage, title: String }, ChatMessage { username: String, content: String }, Calendar { year: i32, month: u8 }, + Cells { rule: u8, rows: u32, scale: u32 }, } #[derive(Default)] @@ -77,6 +79,7 @@ impl Drawer { self.on_chat_message(username, content)? } Command::Calendar { year, month } => self.draw_calendar(year, month)?, + Command::Cells { rule, rows, scale } => self.draw_cells(rule, rows, scale)?, } Ok(()) } diff --git a/showbits-thermal-printer/src/drawer/cells.rs b/showbits-thermal-printer/src/drawer/cells.rs new file mode 100644 index 0000000..8f832d0 --- /dev/null +++ b/showbits-thermal-printer/src/drawer/cells.rs @@ -0,0 +1,89 @@ +use image::{ + imageops::{self, FilterType}, + Rgba, RgbaImage, +}; +use showbits_common::{color, widgets::Image, Node, Tree, WidgetExt}; +use taffy::{ + style_helpers::{length, percent}, + AlignItems, Display, FlexDirection, +}; + +use crate::printer::Printer; + +use super::{Context, Drawer}; + +const BLACK: Rgba = Rgba([0, 0, 0, 255]); +const WHITE: Rgba = Rgba([255, 255, 255, 255]); + +fn b2c(bool: bool) -> Rgba { + match bool { + true => BLACK, + false => WHITE, + } +} + +fn c2b(color: Rgba) -> bool { + color == BLACK +} + +fn neighbors_at(image: &RgbaImage, x: u32, y: u32) -> [bool; 3] { + let left = x + .checked_sub(1) + .map(|x| *image.get_pixel(x, y)) + .unwrap_or(WHITE); + + let mid = *image.get_pixel(x, y); + + let right = image.get_pixel_checked(x + 1, y).copied().unwrap_or(WHITE); + + [c2b(left), c2b(mid), c2b(right)] +} + +fn apply_rule(rule: u8, neighbors: [bool; 3]) -> bool { + let [left, mid, right] = neighbors.map(|n| n as u8); + let index = left << 2 | mid << 1 | right; + rule & (1 << index) != 0 +} + +impl Drawer { + pub fn draw_cells(&mut self, rule: u8, rows: u32, scale: u32) -> anyhow::Result<()> { + let mut image = RgbaImage::new(Printer::WIDTH / scale, rows); + + // Initialize first line randomly + for x in 0..image.width() { + image.put_pixel(x, 0, b2c(rand::random())); + } + + // Calculate next rows + for y in 1..rows { + for x in 0..image.width() { + let neighbors = neighbors_at(&image, x, y - 1); + let state = apply_rule(rule, neighbors); + image.put_pixel(x, y, b2c(state)); + } + } + + let image = imageops::resize( + &image, + image.width() * scale, + image.height() * scale, + FilterType::Nearest, + ); + + let mut tree = Tree::::new(color::WHITE); + + let image = Image::new(image).node().register(&mut tree)?; + + let root = Node::empty() + .with_size_width(percent(1.0)) + .with_padding_bottom(length(Self::FEED)) + .with_display(Display::Flex) + .with_flex_direction(FlexDirection::Column) + .with_align_items(Some(AlignItems::Center)) + .and_child(image) + .register(&mut tree)?; + + self.printer.print_tree(&mut tree, &mut self.ctx, root)?; + Ok(()) + } +} diff --git a/showbits-thermal-printer/src/printer.rs b/showbits-thermal-printer/src/printer.rs index 0313ba6..93b9575 100644 --- a/showbits-thermal-printer/src/printer.rs +++ b/showbits-thermal-printer/src/printer.rs @@ -25,7 +25,7 @@ impl Printer { /// Width of the printable area in pixels. /// /// Assumed to be a multiple of 8, then measured to that precision. - const WIDTH: u32 = 8 * 48; + pub const WIDTH: u32 = 8 * 48; /// Images are printed in chunks because a single print command can only /// print so much data. diff --git a/showbits-thermal-printer/src/server.rs b/showbits-thermal-printer/src/server.rs index c39b954..5f54b29 100644 --- a/showbits-thermal-printer/src/server.rs +++ b/showbits-thermal-printer/src/server.rs @@ -31,6 +31,7 @@ pub async fn run(tx: mpsc::Sender, addr: String) -> anyhow::Result<()> .route("/photo", post(post_photo).fallback(get_static_file)) .route("/chat_message", post(post_chat_message)) .route("/calendar", post(post_calendar)) + .route("/cells", post(post_cells)) .fallback(get(get_static_file)) .layer(DefaultBodyLimit::max(32 * 1024 * 1024)) // 32 MiB .with_state(Server { tx }); @@ -148,3 +149,21 @@ async fn post_calendar(server: State, request: Form) { }) .await; } + +#[derive(Deserialize)] +struct PostCellsForm { + rule: u8, + rows: Option, + scale: Option, +} + +async fn post_cells(server: State, request: Form) { + let _ = server + .tx + .send(Command::Cells { + rule: request.0.rule, + rows: request.0.rows.unwrap_or(32).min(512), + scale: request.0.scale.unwrap_or(4), + }) + .await; +}