2024-07-28 04:01:57 +00:00
|
|
|
use crate::imageproc::{DiffusionMatrix, Ditherer, EInkImage, ErrorDiffusionDither};
|
2024-07-27 19:11:44 +00:00
|
|
|
use axum::extract::Multipart;
|
|
|
|
use axum::http::StatusCode;
|
|
|
|
use axum::response::IntoResponse;
|
|
|
|
use axum::{extract::State, response::Response, routing::post, Router};
|
2024-07-28 04:01:57 +00:00
|
|
|
use image::{ImageReader, RgbImage};
|
2024-07-27 19:11:44 +00:00
|
|
|
use std::io::Cursor;
|
2024-07-28 04:01:57 +00:00
|
|
|
use std::time::Duration;
|
|
|
|
use tracing::{error, info, debug, instrument};
|
2024-07-18 12:34:28 +00:00
|
|
|
|
2024-07-27 19:11:44 +00:00
|
|
|
use crate::display::EInkPanel;
|
2024-07-28 04:01:57 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
use tokio::sync::mpsc::{self, Receiver, Sender};
|
|
|
|
use tokio::task::JoinHandle;
|
2024-07-18 12:34:28 +00:00
|
|
|
|
2024-07-27 19:11:44 +00:00
|
|
|
pub enum ImageFormFields {
|
|
|
|
DitherType,
|
|
|
|
ImageFile,
|
|
|
|
}
|
2024-07-24 14:43:09 +00:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
2024-07-27 19:11:44 +00:00
|
|
|
pub struct Context {
|
2024-07-28 04:01:57 +00:00
|
|
|
display_channel: Sender<DisplaySetCommand>,
|
|
|
|
display_task: Arc<JoinHandle<()>>,
|
2024-07-27 19:11:44 +00:00
|
|
|
}
|
2024-07-28 04:01:57 +00:00
|
|
|
|
2024-07-27 19:11:44 +00:00
|
|
|
impl Context {
|
|
|
|
#[must_use]
|
2024-07-28 04:01:57 +00:00
|
|
|
pub fn new(disp: Box<dyn EInkPanel + Send>) -> Self {
|
|
|
|
let (tx, rx) = mpsc::channel(2);
|
|
|
|
let task = tokio::spawn(display_task(rx, disp));
|
2024-07-27 19:11:44 +00:00
|
|
|
Self {
|
2024-07-28 04:01:57 +00:00
|
|
|
display_channel: tx,
|
|
|
|
display_task: Arc::new(task),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct DisplaySetCommand {
|
|
|
|
img: Box<EInkImage>,
|
|
|
|
}
|
|
|
|
#[instrument(skip_all)]
|
|
|
|
pub async fn display_task(
|
|
|
|
mut rx: Receiver<DisplaySetCommand>,
|
|
|
|
mut display: Box<dyn EInkPanel + Send>,
|
|
|
|
) {
|
|
|
|
while let Some(cmd) = rx.recv().await {
|
|
|
|
info!("Got a display set command");
|
|
|
|
let raw_buf = cmd.img.into_display_buffer();
|
|
|
|
if let Err(e) = display.display(&raw_buf) {
|
|
|
|
error!("Error displaying command {e}");
|
2024-07-27 19:11:44 +00:00
|
|
|
}
|
2024-07-28 04:01:57 +00:00
|
|
|
info!("Done setting display");
|
2024-07-27 19:11:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make our own error that wraps `anyhow::Error`.
|
|
|
|
struct AppError(anyhow::Error);
|
|
|
|
|
|
|
|
// Tell axum how to convert `AppError` into a response.
|
|
|
|
impl IntoResponse for AppError {
|
|
|
|
fn into_response(self) -> Response {
|
|
|
|
(
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
format!("Something went wrong: {}", self.0),
|
|
|
|
)
|
|
|
|
.into_response()
|
|
|
|
}
|
2024-07-24 14:43:09 +00:00
|
|
|
}
|
2024-07-27 19:11:44 +00:00
|
|
|
|
|
|
|
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
|
|
|
|
// `Result<_, AppError>`. That way you don't need to do that manually.
|
|
|
|
impl<E> From<E> for AppError
|
|
|
|
where
|
|
|
|
E: Into<anyhow::Error>,
|
|
|
|
{
|
|
|
|
fn from(err: E) -> Self {
|
|
|
|
Self(err.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-18 12:34:28 +00:00
|
|
|
/// API routes for axum
|
|
|
|
/// Start with the basics: Send an image, crop it, dither, and upload.
|
|
|
|
/// we defer the upload to a separate task.
|
2024-07-27 19:11:44 +00:00
|
|
|
pub fn router() -> Router<Context> {
|
|
|
|
Router::new().route("/setimage", post(set_image))
|
2024-07-24 14:43:09 +00:00
|
|
|
}
|
|
|
|
|
2024-07-28 04:01:57 +00:00
|
|
|
#[instrument(skip(ctx))]
|
2024-07-27 19:11:44 +00:00
|
|
|
async fn set_image(
|
|
|
|
State(ctx): State<Context>,
|
|
|
|
mut parts: Multipart,
|
|
|
|
) -> Result<impl IntoResponse, AppError> {
|
|
|
|
while let Some(field) = parts.next_field().await? {
|
|
|
|
let name = field.name().expect("fields always have names").to_string();
|
|
|
|
let data = field.bytes().await?;
|
2024-07-28 04:01:57 +00:00
|
|
|
debug!("Length of `{}` is {} bytes", name, data.len());
|
2024-07-27 19:11:44 +00:00
|
|
|
if &name == "image" {
|
|
|
|
let reader = ImageReader::new(Cursor::new(data))
|
|
|
|
.with_guessed_format()
|
|
|
|
.expect("Cursor io never fails");
|
2024-07-28 04:01:57 +00:00
|
|
|
debug!("Guessed format: {:?}", reader.format());
|
|
|
|
|
|
|
|
let image = reader.decode()?;
|
|
|
|
let mut buf = EInkImage::new(800, 480);
|
|
|
|
let dither = ErrorDiffusionDither::new(DiffusionMatrix::Atkinson);
|
|
|
|
dither.dither(&image.into(), &mut buf);
|
|
|
|
let cmd = DisplaySetCommand {
|
|
|
|
img: Box::new(buf),
|
|
|
|
};
|
|
|
|
ctx.display_channel.send_timeout(cmd, Duration::from_secs(10)).await?;
|
2024-07-27 19:11:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
2024-07-24 14:43:09 +00:00
|
|
|
}
|