Add /cells
This commit is contained in:
parent
5e9a4277d0
commit
755889e82d
6 changed files with 114 additions and 1 deletions
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
89
showbits-thermal-printer/src/drawer/cells.rs
Normal file
89
showbits-thermal-printer/src/drawer/cells.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue