Add option to export original images

This commit is contained in:
Joscha 2025-04-06 11:46:33 +02:00
parent c8fb228a24
commit dad0f282c6
5 changed files with 51 additions and 20 deletions

View file

@ -1,14 +1,15 @@
#!/usr/bin/env fish
argparse h/help p/print r/release -- $argv
argparse h/help p/print o/originals r/release -- $argv
and not set -ql _flag_help
or begin
echo "Usage:" (status filename) "[OPTIONS]"
echo
echo "Options:"
echo " -h, --help Show this help"
echo " -p, --print Attach to printer at /dev/usb/lp0"
echo " -r, --release Use 'cargo run --release'"
echo " -h, --help Show this help"
echo " -p, --print Attach to printer at /dev/usb/lp0"
echo " -o, --originals Export original images"
echo " -r, --release Use 'cargo run --release'"
return
end
@ -22,6 +23,11 @@ if set -ql _flag_print
set arg_print -p /dev/usb/lp0
end
set -l arg_originals
if set -ql _flag_originals
set arg_originals -o target/originals
end
cargo run $arg_release \
--package showbits-thermal-printer \
-- target/queue -e target/image.png $arg_print
-- target/queue -e target/image.png $arg_print $arg_originals

View file

@ -4,6 +4,7 @@ Description=Showbits Thermal Printer
[Service]
Type=simple
ExecStart=/home/bondrucker/showbits-thermal-printer queue -a 0.0.0.0:8001 -p /dev/usb/lp0
# ExecStart=/home/bondrucker/showbits-thermal-printer queue -a 0.0.0.0:8001 -p /dev/usb/lp0 -o originals
Restart=on-failure
User=bondrucker

View file

@ -1,4 +1,4 @@
use std::io::Cursor;
use std::{fs, io::Cursor};
use anyhow::{Context, anyhow, bail};
use axum::{
@ -10,6 +10,7 @@ use image::{
DynamicImage, EncodableLayout, ImageDecoder, ImageFormat, ImageReader, Luma, Pixel, RgbaImage,
imageops,
};
use jiff::Timestamp;
use mark::dither::{AlgoFloydSteinberg, AlgoStucki, Algorithm, DiffEuclid, Palette};
use palette::LinSrgb;
use serde::Serialize;
@ -101,17 +102,7 @@ pub async fn post(server: State<Server>, mut multipart: Multipart) -> somehow::R
while let Some(field) = multipart.next_field().await? {
match field.name() {
Some("image") => {
let data = field.bytes().await?;
// https://github.com/image-rs/image/issues/2392#issuecomment-2547393362
let mut decoder = ImageReader::new(Cursor::new(data.as_bytes()))
.with_guessed_format()?
.into_decoder()?;
let orientation = decoder.orientation()?;
let mut decoded = DynamicImage::from_decoder(decoder)?;
decoded.apply_orientation(orientation);
image = Some(decoded.to_rgba8());
image = Some(field.bytes().await?);
}
Some("title") => {
data.title = Some(field.text().await?).filter(|it| !it.is_empty());
@ -139,10 +130,31 @@ pub async fn post(server: State<Server>, mut multipart: Multipart) -> somehow::R
return Ok(status_code(StatusCode::UNPROCESSABLE_ENTITY));
};
// Export original image if requested
if let Some(dir) = &server.originals {
fs::create_dir_all(dir)?;
let path = dir.join(Timestamp::now().as_millisecond().to_string());
fs::write(path, &image)?;
}
// Decode image data
let image = {
// https://github.com/image-rs/image/issues/2392#issuecomment-2547393362
let mut decoder = ImageReader::new(Cursor::new(image.as_bytes()))
.with_guessed_format()?
.into_decoder()?;
let orientation = decoder.orientation()?;
let mut decoded = DynamicImage::from_decoder(decoder)?;
decoded.apply_orientation(orientation);
decoded.to_rgba8()
};
// Dither image
let max_width = Some(384);
let max_height = Some(1024);
let image = dither(image, max_width, max_height, bright, &algo).map_err(somehow::Error)?;
// Encode dithered image for typst
let mut bytes: Vec<u8> = Vec::new();
image
.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Png)

View file

@ -33,6 +33,11 @@ struct Args {
/// Export an image of whatever is printed here.
#[arg(long, short)]
export: Option<PathBuf>,
/// Export the original images printed by the image document, before
/// dithering or other manipulation.
#[arg(long, short)]
originals: Option<PathBuf>,
}
fn main() -> anyhow::Result<()> {
@ -44,7 +49,7 @@ fn main() -> anyhow::Result<()> {
let mut drawer = Drawer::new(rx, printer);
let runtime = Runtime::new()?;
runtime.spawn(server::run(tx.clone(), args.address));
runtime.spawn(server::run(tx.clone(), args.address, args.originals));
runtime.spawn(async move {
loop {
let _ = tx.send(Command::Backlog).await;

View file

@ -2,6 +2,8 @@ pub mod somehow;
mod r#static;
pub mod statuscode;
use std::path::PathBuf;
use axum::{
Router,
extract::DefaultBodyLimit,
@ -18,6 +20,7 @@ use crate::{documents, drawer::Command};
#[derive(Clone)]
pub struct Server {
tx: mpsc::Sender<Command>,
pub originals: Option<PathBuf>,
}
impl Server {
@ -28,7 +31,11 @@ impl Server {
}
}
pub async fn run(tx: mpsc::Sender<Command>, addr: String) -> anyhow::Result<()> {
pub async fn run(
tx: mpsc::Sender<Command>,
addr: String,
originals: Option<PathBuf>,
) -> anyhow::Result<()> {
let app = Router::new()
// Files
.route("/", get(r#static::get_index))
@ -47,7 +54,7 @@ pub async fn run(tx: mpsc::Sender<Command>, addr: String) -> anyhow::Result<()>
.route("/api/xkcd", post(documents::xkcd::post))
// Rest
.layer(DefaultBodyLimit::max(32 * 1024 * 1024)) // 32 MiB
.with_state(Server { tx });
.with_state(Server { tx, originals });
let listener = TcpListener::bind(addr).await?;
axum::serve(listener, app).await?;