Add /cells

This commit is contained in:
Joscha 2024-03-17 17:41:08 +01:00
parent 5e9a4277d0
commit 755889e82d
6 changed files with 114 additions and 1 deletions

View file

@ -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(())
}

View file

@ -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<u8> = Rgba([0, 0, 0, 255]);
const WHITE: Rgba<u8> = Rgba([255, 255, 255, 255]);
fn b2c(bool: bool) -> Rgba<u8> {
match bool {
true => BLACK,
false => WHITE,
}
}
fn c2b(color: Rgba<u8>) -> 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::<Context>::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(())
}
}

View file

@ -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.

View file

@ -31,6 +31,7 @@ pub async fn run(tx: mpsc::Sender<Command>, 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<Server>, request: Form<PostCalendarForm>) {
})
.await;
}
#[derive(Deserialize)]
struct PostCellsForm {
rule: u8,
rows: Option<u32>,
scale: Option<u32>,
}
async fn post_cells(server: State<Server>, request: Form<PostCellsForm>) {
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;
}