Compare commits

..

No commits in common. "ad2426561ff5075d558c1261d1c67d124f28f085" and "35ef64a829d01b67c5373f4d6528d76648afda38" have entirely different histories.

5 changed files with 64 additions and 83 deletions

77
Cargo.lock generated
View file

@ -26,7 +26,7 @@ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy 0.7.35", "zerocopy",
] ]
[[package]] [[package]]
@ -311,9 +311,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.16.3" version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -329,9 +329,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.7.1" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
[[package]] [[package]]
name = "cast" name = "cast"
@ -341,9 +341,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.7" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -394,9 +394,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.13" version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -404,9 +404,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.13" version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -416,9 +416,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.13" version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@ -910,9 +910,9 @@ checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.3.0" version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
@ -1143,9 +1143,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "minijinja" name = "minijinja"
version = "2.1.1" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4bf71af278c578cbcc91d0b1ff092910208bc86f7b3750364642bd424e3dcd3" checksum = "45f7e8e35b6c7b169bf40b0176d2c79291ab8ee53290b84e0668ab21d841aa9d"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -1555,12 +1555,9 @@ dependencies = [
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.18" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
dependencies = [
"zerocopy 0.6.6",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
@ -1829,12 +1826,11 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.122" version = "1.0.120"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr",
"ryu", "ryu",
"serde", "serde",
] ]
@ -2038,9 +2034,9 @@ dependencies = [
[[package]] [[package]]
name = "target-lexicon" name = "target-lexicon"
version = "0.12.16" version = "0.12.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
@ -2095,9 +2091,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.39.2" version = "1.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -2514,34 +2510,13 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "zerocopy"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6"
dependencies = [
"byteorder",
"zerocopy-derive 0.6.6",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.35" version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [ dependencies = [
"zerocopy-derive 0.7.35", "zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
dependencies = [
"proc-macro2",
"quote",
"syn",
] ]
[[package]] [[package]]

View file

@ -6,13 +6,14 @@ use axum::extract::{FromRequest, Multipart, State};
use axum::http::{header, StatusCode}; use axum::http::{header, StatusCode};
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::{response::Response, routing::post, Router}; use axum::{response::Response, routing::post, Router};
use image::{imageops::resize, imageops::FilterType, ImageReader, RgbImage}; use image::{ImageReader, RgbImage};
use std::io::Cursor; use std::io::Cursor;
use std::str; use std::str;
use std::str::FromStr; use std::str::FromStr;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::Arc; use std::sync::Arc;
use std::thread::JoinHandle; use std::thread::JoinHandle;
use std::time::Duration;
use tracing::{error, info, instrument}; use tracing::{error, info, instrument};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -127,20 +128,21 @@ where
} }
} }
#[instrument(skip_all)] #[instrument(skip(ctx))]
async fn set_image( async fn set_image(
State(ctx): State<AppState>, State(ctx): State<AppState>,
img_req: ImageRequest, img_req: ImageRequest,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
// FIXME: resize image to 800x480 to match the eink panel. // FIXME: resize image to 800x480 to match the eink panel.
info!("Got image"); let mut buf = DitheredImage::new(
let mut buf = DitheredImage::new(800, 480, img_req.palette.value().to_vec()); img_req.image.width(),
let resized = resize(&*img_req.image, 800, 480, FilterType::Lanczos3); img_req.image.height(),
img_req.palette.value().to_vec(),
);
{ {
let mut dither = img_req.dither_method.get_ditherer(); let mut dither = img_req.dither_method.get_ditherer();
dither.dither(&resized, &mut buf); dither.dither(&img_req.image, &mut buf);
} }
info!("image resized, pushing to channel");
ctx.display_channel.send(Box::new(buf))?; ctx.display_channel.send(Box::new(buf))?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
@ -148,11 +150,14 @@ async fn set_image(
/// generates a dithered image based on the given image and the dithering parameters. /// generates a dithered image based on the given image and the dithering parameters.
/// Can be used to see how the dithering and palette choices affect the result. /// Can be used to see how the dithering and palette choices affect the result.
async fn preview_image(img_req: ImageRequest) -> Result<impl IntoResponse, AppError> { async fn preview_image(img_req: ImageRequest) -> Result<impl IntoResponse, AppError> {
let mut buf = DitheredImage::new(800, 480, img_req.palette.value().to_vec()); let mut buf = DitheredImage::new(
let resized = resize(&*img_req.image, 800, 480, FilterType::Lanczos3); img_req.image.width(),
img_req.image.height(),
img_req.palette.value().to_vec(),
);
{ {
let mut dither = img_req.dither_method.get_ditherer(); let mut dither = img_req.dither_method.get_ditherer();
dither.dither(&resized, &mut buf); dither.dither(&img_req.image, &mut buf);
} }
// Convert buf into a png image. // Convert buf into a png image.
let img = buf.into_rgbimage(); let img = buf.into_rgbimage();

View file

@ -39,8 +39,8 @@ pub enum ProcessingError {
/// Buffer to be sent to the ``EInk`` display. /// Buffer to be sent to the ``EInk`` display.
#[derive(Debug)] #[derive(Debug)]
pub struct DitheredImage { pub struct DitheredImage {
pub buf: ImageBuffer<Luma<u8>, Vec<u8>>, buf: ImageBuffer<Luma<u8>, Vec<u8>>,
pub palette: Vec<Srgb>, palette: Vec<Srgb>,
} }
impl DitheredImage { impl DitheredImage {
@ -86,7 +86,8 @@ pub trait Ditherer {
fn dither(&mut self, img: &RgbImage, output: &mut DitheredImage); fn dither(&mut self, img: &RgbImage, output: &mut DitheredImage);
} }
/// Find the closest approximate palette color /// Find the closest approximate palette color to the given sRGB value.
/// This uses euclidian distance in linear space.
fn nearest_neighbor(input_color: Lab, palette: &[Lab]) -> (u8, Lab) { fn nearest_neighbor(input_color: Lab, palette: &[Lab]) -> (u8, Lab) {
let (nearest, _, color_diff) = palette let (nearest, _, color_diff) = palette
.iter() .iter()
@ -94,7 +95,7 @@ fn nearest_neighbor(input_color: Lab, palette: &[Lab]) -> (u8, Lab) {
.map(|(idx, p_color)| { .map(|(idx, p_color)| {
( (
idx, idx,
input_color.difference(*p_color), // this is CIEDIE2000 based and highly accurate. input_color.difference(*p_color),
input_color - *p_color, input_color - *p_color,
) )
}) })
@ -198,7 +199,7 @@ static STUKI_DITHER_POINTS: &[DiffusionPoint] = &[
pub type DiffusionMatrix<'a> = &'a [DiffusionPoint]; pub type DiffusionMatrix<'a> = &'a [DiffusionPoint];
#[derive(Debug)] #[derive(Debug)]
pub struct ErrorDiffusion<'a>(DiffusionMatrix<'a>); pub struct ErrorDiffusion<'a>(&'a [DiffusionPoint]);
impl<'a> ErrorDiffusion<'a> { impl<'a> ErrorDiffusion<'a> {
#[must_use] #[must_use]

View file

@ -3,8 +3,8 @@ pub mod display;
pub mod dither; pub mod dither;
pub mod eink; pub mod eink;
use serde::Deserialize;
use std::path::PathBuf; use std::path::PathBuf;
use serde::Deserialize;
use toml; use toml;
use crate::display::{EInkPanel, FakeEInk, Wrapper}; use crate::display::{EInkPanel, FakeEInk, Wrapper};
@ -12,13 +12,16 @@ use crate::dither::{DitherMethod, DitheredImage};
use crate::eink::Palette; use crate::eink::Palette;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use image::RgbImage; use image::RgbImage;
use tracing::{error, event, info, warn, Level}; use tracing::{error, info};
/// Application config, including sqlite db path, scan folders, and scheduling. /// Application config, including sqlite db path, scan folders, and scheduling.
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct Config { struct Config {
database_path: PathBuf, database_path: PathBuf,
search_paths: Vec<PathBuf>, search_paths: Vec<PathBuf>,
} }
/// Display images on E-Ink Displays /// Display images on E-Ink Displays
@ -35,6 +38,8 @@ enum Command {
Convert(ConvertArgs), Convert(ConvertArgs),
/// Load a single image /// Load a single image
Show, Show,
/// Display a test pattern
Test,
/// Start the HTTP server /// Start the HTTP server
Serve, Serve,
} }
@ -52,18 +57,6 @@ async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let cli = Cli::parse(); let cli = Cli::parse();
let mut display: Box<dyn EInkPanel + Send> = match Wrapper::new() {
Ok(w) => {
info!("Found real hardware, using it");
Box::new(w)
}
Err(e) => {
event!(Level::WARN, "Error opening display SPI interface: {e}");
warn!("Falling back to fake display");
Box::new(FakeEInk {})
}
};
match cli.command { match cli.command {
Command::Convert(a) => { Command::Convert(a) => {
let input = image::ImageReader::open(a.input_file)? let input = image::ImageReader::open(a.input_file)?
@ -83,6 +76,7 @@ async fn main() -> anyhow::Result<()> {
Command::Show => { Command::Show => {
let img: RgbImage = image::ImageReader::open("image.png")?.decode()?.into(); let img: RgbImage = image::ImageReader::open("image.png")?.decode()?.into();
error!("HI"); error!("HI");
let mut display = FakeEInk {};
let mut eink_buf = crate::eink::new_image(); let mut eink_buf = crate::eink::new_image();
let mut dither = DitherMethod::Atkinson.get_ditherer(); let mut dither = DitherMethod::Atkinson.get_ditherer();
@ -90,8 +84,15 @@ async fn main() -> anyhow::Result<()> {
dither.dither(&img, &mut eink_buf); dither.dither(&img, &mut eink_buf);
display.display(&eink_buf)?; display.display(&eink_buf)?;
} }
Command::Test => {
let mut display = Wrapper::new()?;
display.test()?;
}
Command::Serve => { Command::Serve => {
let ctx = api::AppState::new(display); let display = FakeEInk {};
let ctx = api::AppState::new(Box::new(display));
let app = api::router().with_state(ctx); let app = api::router().with_state(ctx);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;

View file

@ -1,4 +1,3 @@
POST http://192.168.0.185:3000/setimage POST http://192.168.0.185:3000/setimage
[MultipartFormData] [MultipartFormData]
image: file,{{image}}; image: file,image.png;
dither_method: Atkinson