diff --git a/Cargo.lock b/Cargo.lock index 7d3ccdb..473983e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2109,6 +2109,16 @@ dependencies = [ "libc", ] +[[package]] +name = "matrixmultiply" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -2219,6 +2229,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + [[package]] name = "ndk" version = "0.8.0" @@ -2325,6 +2348,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -2684,10 +2716,10 @@ dependencies = [ "anyhow", "clap", "eframe", - "egui", "epd-waveshare", "image", "linux-embedded-hal", + "ndarray", "num-traits", "palette", "rayon", @@ -2951,6 +2983,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.10.0" diff --git a/Cargo.toml b/Cargo.toml index 26c8634..5d9d2ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,18 @@ edition = "2021" gui = [ "dep:eframe" ] eink = [ "dep:epd-waveshare", "dep:linux-embedded-hal" ] +[[bin]] +name = "inspector" +required-features = ["gui"] + [dependencies] anyhow = "1.0.86" clap = { version = "4.5.7", features = ["derive"] } -eframe = "0.28.0" -epd-waveshare = { git = "https://github.com/caemor/epd-waveshare.git" } +eframe = { version = "0.28.0", optional = true } +epd-waveshare = { git = "https://github.com/caemor/epd-waveshare.git", optional = true } image = "0.25.1" -linux-embedded-hal = { version = "0.4.0" } +linux-embedded-hal = { version = "0.4.0", optional = true } +ndarray = "0.15.6" num-traits = "0.2.19" palette = "0.7.6" rayon = "1.10.0" diff --git a/src/display.rs b/src/display.rs index 501da03..7190e49 100644 --- a/src/display.rs +++ b/src/display.rs @@ -6,15 +6,15 @@ use linux_embedded_hal::{CdevPin, Delay, SpidevDevice}; use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags}; use anyhow::Result; -pub struct DisplayWrapper { +pub struct Wrapper { spi: SpidevDevice, gpiochip: Chip, delay: Delay, panel: Epd7in3f } -impl DisplayWrapper { - pub fn new() -> Result { +impl Wrapper { + pub fn new() -> Result { let mut spi = SpidevDevice::open("/dev/spidev0.0")?; let spi_options = SpidevOptions::new() .bits_per_word(8) @@ -42,7 +42,7 @@ impl DisplayWrapper { let mut delay = Delay {}; let panel = Epd7in3f::new(&mut spi, busy_pin, dc_pin, rst_pin, &mut delay, None)?; - Ok(DisplayWrapper { + Ok(Wrapper { spi, gpiochip, delay, diff --git a/src/imageproc.rs b/src/imageproc.rs index 9300e6a..11d56e1 100644 --- a/src/imageproc.rs +++ b/src/imageproc.rs @@ -1,7 +1,6 @@ - use image::RgbImage; -use palette::{cast::FromComponents, color_difference::Ciede2000, IntoColor, Lab, Srgb}; +use palette::{cast::FromComponents, color_difference::Ciede2000, IntoColor, Lab, Oklch, Srgb}; /// Palette used on the display; pixels can be one of these colors. /// @@ -56,7 +55,7 @@ impl DisplayColor { 4 => Self::Red, 5 => Self::Yellow, 6 => Self::Orange, - _ => panic!("unexpected DisplayColor {}", value), + _ => panic!("unexpected DisplayColor {value}"), } } @@ -68,16 +67,17 @@ impl DisplayColor { } impl From for u8 { fn from(value: DisplayColor) -> Self { - value as u8 + value as Self } } -/// Buffer to be sent to the EInk display. +/// Buffer to be sent to the ``EInk`` display. #[derive(Debug)] pub struct EInkBuffer(Vec); impl EInkBuffer { + #[must_use] pub fn into_display_buffer(&self) -> Vec { - let mut buf = Vec::with_capacity(self.0.len()/2); + let mut buf = Vec::with_capacity(self.0.len() / 2); for colors in self.0.chunks_exact(2) { buf.push(DisplayColor::into_byte(colors[0], colors[1])); @@ -85,9 +85,10 @@ impl EInkBuffer { buf } + #[must_use] pub fn new(width: usize, height: usize) -> Self { let v = vec![DisplayColor::Black; width * height]; - EInkBuffer(v) + Self(v) } } @@ -109,12 +110,12 @@ impl EInkBuffer { // self.data[x + y * self.width] = value; // } // pub fn get(&self, x:usize, y:usize) -> DisplayColor { -// self.data[x +// self.data[x // } // } pub trait Ditherer { - fn dither(&self, img: &RgbImage, output: &mut EInkBuffer); + fn dither(&mut self, img: &RgbImage, output: &mut EInkBuffer); } // fn color_distance(c1: LinSrgb, c2: LinSrgb) -> f32 { @@ -127,36 +128,46 @@ pub trait Ditherer { /// Find the closest approximate palette color to the given sRGB value. /// This uses euclidian distance in linear space. -pub fn nearest_neighbor(color: Srgb) -> (DisplayColor, f32) { - let input: Lab = color.into_color(); - - let (nearest, dist) = DISPLAY_PALETTE +pub fn nearest_neighbor(input_color: Lab) -> (DisplayColor, Lab) { + let (nearest, _, color_diff) = DISPLAY_PALETTE .iter() .enumerate() .map(|(idx, p_color)| { let c: Lab = (*p_color).into_color(); - (idx, input.difference(c)) + (idx, input_color.difference(c), input_color - c) }) - .min_by(|(_, a), (_, b)| a.total_cmp(b)) + .min_by(|(_, a, _), (_, b, _)| a.total_cmp(b)) .unwrap(); - (DisplayColor::from_u8(nearest as u8), dist) + (DisplayColor::from_u8(nearest as u8), color_diff) } pub struct NNDither(); impl Ditherer for NNDither { - fn dither(&self, img: &RgbImage, output: &mut EInkBuffer) { + fn dither(&mut self, img: &RgbImage, output: &mut EInkBuffer) { assert!(img.width() == 800); assert!(img.height() == 480); - + // sRGB view into the given image. zero copy! let srgb = <&[Srgb]>::from_components(&**img); for (idx, pixel) in srgb.iter().enumerate() { - let (n, _) = nearest_neighbor(pixel.into_format()); + let (n, _) = nearest_neighbor(pixel.into_format().into_color()); output.0[idx] = n; } - } } + +pub struct FloydSteinbergDither(); + +impl Ditherer for FloydSteinbergDither { + fn dither(&mut self, img: &RgbImage, output: &mut EInkBuffer) { + // create a copy of the image in Lab space, mutable. + let srgb = <&[Srgb]>::from_components(&**img); + let mut temp_img: Vec = Vec::new(); + for pix in srgb { + temp_img.push(pix.into_format().into_color()); + } + } +} diff --git a/src/main.rs b/src/main.rs index 0c6ad6f..1d031d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ +#[cfg(feature = "eink")] pub mod display; pub mod imageproc; -use crate::display::DisplayWrapper; +use crate::display::Wrapper; use crate::imageproc::{Ditherer, EInkBuffer, NNDither}; use clap::{Parser, Subcommand}; use image::RgbImage; @@ -28,7 +29,7 @@ fn main() -> anyhow::Result<()> { if let Command::Show = cli.command { let img: RgbImage = image::io::Reader::open("myimage.png")?.decode()?.into(); - let mut display = DisplayWrapper::new()?; + let mut display = Wrapper::new()?; let mut eink_buf = EInkBuffer::new(800, 480); let dither = NNDither {}; diff --git a/src/ui.rs b/src/ui.rs deleted file mode 100644 index e69de29..0000000