diff --git a/Cargo.lock b/Cargo.lock index 6d4d05d..3accf1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -267,6 +267,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bit_field" version = "0.10.2" @@ -1488,6 +1494,7 @@ dependencies = [ "anyhow", "axum", "axum-macros", + "base64", "clap", "criterion", "epd-waveshare", diff --git a/Cargo.toml b/Cargo.toml index 7de574c..a68dd69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" anyhow = "1.0.86" axum = { version = "0.7.5", features = ["macros", "multipart"] } axum-macros = "0.4.1" +base64 = "0.22.1" clap = { version = "4.5.7", features = ["derive"] } epd-waveshare = { git = "https://github.com/caemor/epd-waveshare.git"} image = "0.25.1" diff --git a/src/api.rs b/src/api.rs index b44e7e5..75b9464 100644 --- a/src/api.rs +++ b/src/api.rs @@ -23,10 +23,10 @@ pub fn router() -> Router { } #[derive(Debug)] -struct ImageRequest { - image: Box, - dither_method: DitherMethod, - palette: Palette, +pub struct ImageRequest { + pub image: Box, + pub dither_method: DitherMethod, + pub palette: Palette, } #[async_trait] @@ -36,6 +36,7 @@ where { type Rejection = AppError; + #[instrument(skip_all)] async fn from_request(req: axum::extract::Request, state: &S) -> Result { let mut parts = Multipart::from_request(req, state).await?; let mut img = None; diff --git a/src/app.rs b/src/app.rs index 39aa535..5157dc6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,16 +1,21 @@ use crate::api; use crate::display::create_display_thread; -use crate::dither::DitheredImage; -use axum::extract::{Path, State}; +use crate::dither::{DitherMethod, DitheredImage}; +use crate::eink::Palette; +use axum::extract::State; use axum::http::{header, StatusCode}; use axum::response::{IntoResponse, Response}; -use axum::routing::get; +use axum::routing::{get, post}; use axum::Router; +use base64::{engine::general_purpose::STANDARD, Engine as _}; +use image::imageops::{resize, FilterType}; use include_dir::{include_dir, Dir, DirEntry}; -use minijinja::Environment; +use minijinja::{context, Environment}; +use std::io::Cursor; use std::sync::mpsc; use std::sync::Arc; use std::thread::JoinHandle; +use strum::VariantNames; use tracing::{debug, warn}; use tracing::{error, info, instrument}; @@ -100,26 +105,54 @@ fn make_environment() -> Result, anyhow::Error> { pub fn make_app_router() -> Router { Router::new() - .route("/app/*path", get(app_handler)) + .route("/app", get(app_handler)) + .route("/app/preview", post(app_preview)) .nest("/api", api::router()) .route("/assets", get(asset_handler)) .with_state(AppState::default()) } #[instrument(skip(ctx))] -async fn app_handler( - State(ctx): State, - Path(path): Path, -) -> Result { - let template = ctx.templates.get_template(&path.to_string())?; +async fn app_handler(State(ctx): State) -> Result { + let template = ctx.templates.get_template("app.html")?; - let content = template.render("")?; + let content = template.render(context! { + palettes => Palette::VARIANTS, + dither_methods => DitherMethod::VARIANTS, + version => env!("CARGO_PKG_VERSION"), + })?; let headers = [(header::CONTENT_TYPE, mime::TEXT_HTML_UTF_8.to_string())]; Ok((StatusCode::OK, headers, content)) } +async fn app_preview( + State(ctx): State, + img_req: api::ImageRequest, +) -> Result { + let mut buf = DitheredImage::new(800, 480, img_req.palette.value().to_vec()); + let resized = resize(&*img_req.image, 800, 480, FilterType::Lanczos3); + { + let mut dither = img_req.dither_method.get_ditherer(); + dither.dither(&resized, &mut buf); + } + // Convert buf into a png image. + let img = buf.into_rgbimage(); + + let mut buffer = Cursor::new(Vec::new()); + img.write_to(&mut buffer, image::ImageFormat::Png)?; + + let template = ctx.templates.get_template("preview.html")?; + + let datavec = STANDARD.encode(buffer.into_inner()); + let content = template.render(context! { + image_contents => datavec + })?; + let headers = [(header::CONTENT_TYPE, mime::TEXT_HTML_UTF_8.to_string())]; + Ok((StatusCode::OK, headers, content)) +} + async fn asset_handler() -> Result { Ok(StatusCode::OK) } diff --git a/src/dither.rs b/src/dither.rs index 4e881a8..be33413 100644 --- a/src/dither.rs +++ b/src/dither.rs @@ -9,7 +9,15 @@ use palette::{cast::FromComponents, IntoColor, Lab, Srgb}; use rayon::prelude::*; #[derive( - strum::EnumString, strum::Display, Serialize, Deserialize, PartialEq, Eq, Debug, Clone, + strum::EnumString, + strum::Display, + Serialize, + Deserialize, + PartialEq, + Eq, + Debug, + Clone, + strum::VariantNames, )] pub enum DitherMethod { NearestNeighbor, diff --git a/src/eink.rs b/src/eink.rs index db00c41..a2528af 100644 --- a/src/eink.rs +++ b/src/eink.rs @@ -29,7 +29,16 @@ const SIMPLE_PALETTE: [Srgb; 7] = [ ]; #[derive( - strum::EnumString, strum::Display, Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy, + strum::EnumString, + strum::Display, + Serialize, + Deserialize, + PartialEq, + Eq, + Debug, + Clone, + Copy, + strum::VariantNames, )] pub enum Palette { Default, diff --git a/templates/app.html b/templates/app.html new file mode 100644 index 0000000..5ef1b13 --- /dev/null +++ b/templates/app.html @@ -0,0 +1,63 @@ +{% extends "partials/base.html" %} + +{% block title %} Pi Frame Server {% endblock %} + + +{% block content %} +
+
+

Pi EINK Frame Server

+

Display things accurately (ish)

+
+
+
+
+
+
+ Target Palette + {% for m in palettes %} + + + {% endfor %} +
+ "Default" uses a real-life based color scheme. "Simple" uses an idealized palette. +
+ +
+ + + + Different dither methods produce better results for certain images. + +
+
+ + + + Images will be resized to 800x480, which may stretch the image. + +
+ + +
+
+ +
+ +
+ pi-frame-server Version {{ version }} +
+ +{% endblock %} diff --git a/templates/base.html b/templates/base.html deleted file mode 100644 index abb64b8..0000000 --- a/templates/base.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - Hello Bulma! - - - -
-
-

- Hello World -

-

- My first website with Bulma! -

-
-
- - diff --git a/templates/partials/base.html b/templates/partials/base.html new file mode 100644 index 0000000..6717ef3 --- /dev/null +++ b/templates/partials/base.html @@ -0,0 +1,21 @@ + + + + + + {% block title %} My Site {% endblock %} + + + {% block head %}{% endblock %} + + + {% block content %} +

+ Hello World +

+

+ My first website with Bulma! +

+ {% endblock %} + + diff --git a/templates/preview.html b/templates/preview.html new file mode 100644 index 0000000..87ec00e --- /dev/null +++ b/templates/preview.html @@ -0,0 +1,4 @@ +
+ + +