diff --git a/Cargo.lock b/Cargo.lock index 553f3c3..036f784 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1420,6 +1420,7 @@ dependencies = [ "strum", "thiserror", "tokio", + "toml", "tower-http", "tracing", "tracing-subscriber", @@ -2057,9 +2058,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -2069,18 +2070,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.17" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", @@ -2434,9 +2435,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.16" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index b9319a4..493e3f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ serde = { version = "1.0.204", features = ["derive"] } strum = { version = "0.26.3", features = ["derive"] } thiserror = "1.0.63" tokio = { version = "1.38.1", features = ["full"] } +toml = "0.8.19" tower-http = { version = "0.5.2", features = ["trace"] } tracing = "0.1.40" tracing-subscriber = "0.3.18" @@ -29,4 +30,4 @@ criterion = { version = "0.5", features = ["html_reports"] } [[bench]] name = "dithering" -harness = false \ No newline at end of file +harness = false diff --git a/benches/dithering.rs b/benches/dithering.rs index f0aa944..1e1f976 100644 --- a/benches/dithering.rs +++ b/benches/dithering.rs @@ -1,6 +1,7 @@ use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use image::{ImageReader, RgbImage}; -use pi_frame_server::dither::{DitherMethod, DitheredImage, Palette}; +use pi_frame_server::dither::{DitherMethod, DitheredImage}; +use pi_frame_server::eink::Palette; fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("dithering_benchmark"); diff --git a/src/api.rs b/src/api.rs index 819be09..90f584b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,5 +1,6 @@ use crate::display::EInkPanel; -use crate::dither::{DitherMethod, Palette, DitheredImage}; +use crate::dither::{DitherMethod, DitheredImage}; +use crate::eink::Palette; use axum::async_trait; use axum::extract::{FromRequest, Multipart, State}; use axum::http::{header, StatusCode}; diff --git a/src/dither.rs b/src/dither.rs index d192cc3..33a55ee 100644 --- a/src/dither.rs +++ b/src/dither.rs @@ -7,45 +7,6 @@ use image::Rgb as imgRgb; use palette::color_difference::{Ciede2000, HyAb}; use palette::{cast::FromComponents, IntoColor, Lab, Srgb}; -/// Palette used on the display; pixels can be one of these colors. -/// -/// The RGB values are slightly adjusted to improve accuracy. -const DISPLAY_PALETTE: [Srgb; 7] = [ - Srgb::new(0.047, 0.047, 0.055), // Black - Srgb::new(0.824, 0.824, 0.816), // White - Srgb::new(0.118, 0.376, 0.122), // Green - Srgb::new(0.114, 0.118, 0.667), // Blue - Srgb::new(0.549, 0.106, 0.114), // Red - Srgb::new(0.827, 0.788, 0.239), // Yellow - Srgb::new(0.757, 0.443, 0.165), // Orange -]; - -const SIMPLE_PALETTE: [Srgb; 7] = [ - Srgb::new(0.0, 0.0, 0.0), // Black - Srgb::new(1.0, 1.0, 1.0), // White - Srgb::new(0.0, 1.0, 0.0), // Green - Srgb::new(0.0, 0.0, 1.0), // Blue - Srgb::new(1.0, 0.0, 0.0), // Red - Srgb::new(1.0, 1.0, 0.0), // Yellow - Srgb::new(0.757, 0.443, 0.165), // Orange -]; - -#[derive(strum::EnumString, Serialize, Deserialize, PartialEq, Eq, Debug)] -pub enum Palette { - Default, - Simple, -} - -impl Palette { - #[must_use] - pub const fn value(&self) -> &[Srgb] { - match self { - Self::Default => &DISPLAY_PALETTE, - Self::Simple => &SIMPLE_PALETTE, // FIXME: use simple pallete based on binary. - } - } -} - #[derive( strum::EnumString, strum::Display, Serialize, Deserialize, PartialEq, Eq, Debug, Clone, )] @@ -121,12 +82,6 @@ impl DitheredImage { } } -impl Default for DitheredImage { - fn default() -> Self { - Self::new(800, 480, DISPLAY_PALETTE.to_vec()) - } -} - pub trait Ditherer { fn dither(&mut self, img: &RgbImage, output: &mut DitheredImage); } @@ -138,7 +93,11 @@ fn nearest_neighbor(input_color: Lab, palette: &[Lab]) -> (u8, Lab) { .iter() .enumerate() .map(|(idx, p_color)| { - (idx, input_color.difference(*p_color), input_color - *p_color) + ( + idx, + input_color.difference(*p_color), + input_color - *p_color, + ) }) .min_by(|(_, a, _), (_, b, _)| a.total_cmp(b)) .expect("Should always find a color"); @@ -170,13 +129,7 @@ const fn coord_to_idx(x: u32, y: u32, xsize: u32) -> usize { /// Compute the error-adjusted new lab value based on the error value of the currently scanned /// pixel multiplied by a scalar factor. fn compute_error_adjusted_color(orig: &Lab, err: &Lab, weight: f32) -> Lab { - let (orig_l, orig_a, orig_b) = orig.into_components(); - let (err_l, err_a, err_b) = err.into_components(); - Lab::from_components(( - err_l.mul_add(weight, orig_l), // scalar * err_l + p_l - err_a.mul_add(weight, orig_a), - err_b.mul_add(weight, orig_b), - )) + *orig + *err * weight } /// ``DiffusionPoint`` is part of the diffusion matrix, represented by a shift in x and y and an error diff --git a/src/eink.rs b/src/eink.rs new file mode 100644 index 0000000..87df5f6 --- /dev/null +++ b/src/eink.rs @@ -0,0 +1,56 @@ +use palette::Srgb; +use serde::{Deserialize, Serialize}; + +use crate::dither::DitheredImage; + +/// Palette used on the display; pixels can be one of these colors. +/// +/// The RGB values are slightly adjusted to improve accuracy. +const DISPLAY_PALETTE: [Srgb; 7] = [ + Srgb::new(0.047, 0.047, 0.055), // Black + Srgb::new(0.824, 0.824, 0.816), // White + Srgb::new(0.118, 0.376, 0.122), // Green + Srgb::new(0.114, 0.118, 0.667), // Blue + Srgb::new(0.549, 0.106, 0.114), // Red + Srgb::new(0.827, 0.788, 0.239), // Yellow + Srgb::new(0.757, 0.443, 0.165), // Orange +]; + + + +/// A more primitive palette that doesn't reflect reality. Here for posterity. +/// This is based on the datasheet, not on empirical testing +const SIMPLE_PALETTE: [Srgb; 7] = [ + Srgb::new(0.0, 0.0, 0.0), // Black + Srgb::new(1.0, 1.0, 1.0), // White + Srgb::new(0.0, 1.0, 0.0), // Green + Srgb::new(0.0, 0.0, 1.0), // Blue + Srgb::new(1.0, 0.0, 0.0), // Red + Srgb::new(1.0, 1.0, 0.0), // Yellow + Srgb::new(0.757, 0.443, 0.165), // Orange +]; + +#[derive(strum::EnumString, Serialize, Deserialize, PartialEq, Eq, Debug)] +pub enum Palette { + Default, + Simple, +} + +impl Palette { + #[must_use] + pub const fn value(&self) -> &[Srgb] { + match self { + Self::Default => &DISPLAY_PALETTE, + Self::Simple => &SIMPLE_PALETTE, + } + } +} + + + +/// Construct a dithered image for the EINK display using the default palette and correct +/// resolution. +#[must_use] +pub fn new_image() -> DitheredImage { + DitheredImage::new(800,480, DISPLAY_PALETTE.to_vec()) +} diff --git a/src/lib.rs b/src/lib.rs index 99c74cd..cc4304a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ pub mod dither; +pub mod eink; diff --git a/src/main.rs b/src/main.rs index f2d5c73..6286bd9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,15 +2,27 @@ pub mod api; pub mod display; pub mod dither; pub mod errors; +pub mod eink; use std::path::PathBuf; +use serde::Deserialize; +use toml; use crate::display::{EInkPanel, FakeEInk, Wrapper}; -use crate::dither::{DitherMethod, DitheredImage, Palette}; +use crate::dither::{DitherMethod, DitheredImage}; +use crate::eink::Palette; use clap::{Args, Parser, Subcommand}; use image::RgbImage; use tracing::{error, info}; + + +/// Application config, including sqlite db path, scan folders, and scheduling. +#[derive(Deserialize)] +struct Config { + +} + /// Display images on E-Ink Displays #[derive(Debug, Parser)] #[command(version, about)] @@ -43,7 +55,6 @@ struct ConvertArgs { async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let cli = Cli::parse(); - println!("CLI {cli:?}"); match cli.command { Command::Convert(a) => { @@ -66,7 +77,7 @@ async fn main() -> anyhow::Result<()> { error!("HI"); let mut display = FakeEInk {}; - let mut eink_buf = DitheredImage::default(); + let mut eink_buf = crate::eink::new_image(); let mut dither = DitherMethod::Atkinson.get_ditherer(); dither.dither(&img, &mut eink_buf); diff --git a/tests/dither.rs b/tests/dither.rs index 92d41d0..11e4192 100644 --- a/tests/dither.rs +++ b/tests/dither.rs @@ -6,7 +6,8 @@ use anyhow::Result; use image::{ImageReader, RgbImage}; -use pi_frame_server::dither::{DitherMethod, DitheredImage, Palette}; +use pi_frame_server::dither::{DitherMethod, DitheredImage}; +use pi_frame_server::eink::Palette; fn compare_original(sample_file: &str, reference_file: &str, method: &DitherMethod) -> Result<()> { let image: RgbImage = ImageReader::open(format!("samples/{sample_file}"))?