diff --git a/src/display.rs b/src/display.rs index 7190e49..2c45192 100644 --- a/src/display.rs +++ b/src/display.rs @@ -51,6 +51,12 @@ impl Wrapper { } pub fn display(&mut self, buf: &[u8]) -> Result<()>{ self.panel.update_and_display_frame(&mut self.spi, buf, &mut self.delay)?; + self.panel.sleep(&mut self.spi, &mut self.delay)?; + Ok(()) + } + pub fn test(&mut self) -> Result<()> { + self.panel.show_7block(&mut self.spi, &mut self.delay)?; + self.panel.sleep(&mut self.spi, &mut self.delay)?; Ok(()) } } diff --git a/src/imageproc.rs b/src/imageproc.rs index 11d56e1..16c353b 100644 --- a/src/imageproc.rs +++ b/src/imageproc.rs @@ -1,6 +1,7 @@ use image::RgbImage; -use palette::{cast::FromComponents, color_difference::Ciede2000, IntoColor, Lab, Oklch, Srgb}; +use palette::{cast::FromComponents, IntoColor, Lab, Oklch, Srgb}; +use palette::color_difference::{Ciede2000, EuclideanDistance}; /// Palette used on the display; pixels can be one of these colors. /// @@ -90,6 +91,11 @@ impl EInkBuffer { let v = vec![DisplayColor::Black; width * height]; Self(v) } + // pub fn make_image(&self) -> RgbImage { + // RgbImage::from_fn(800, 480, |x, y| { + // let srgb = Srgb::from(self.0[y * 800 + x]); + // }) + // } } // impl EInkBuffer { @@ -128,16 +134,17 @@ pub trait Ditherer { /// Find the closest approximate palette color to the given sRGB value. /// This uses euclidian distance in linear space. +#[must_use] 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_color.difference(c), input_color - c) + (idx, input_color.distance_squared(c), input_color - c) }) .min_by(|(_, a, _), (_, b, _)| a.total_cmp(b)) - .unwrap(); + .expect("could not find a color"); (DisplayColor::from_u8(nearest as u8), color_diff) } @@ -158,16 +165,85 @@ impl Ditherer for NNDither { } } - pub struct FloydSteinbergDither(); +/// Compute the vector index for a given image by using the size of rows. Assumes that images +/// are indexed in row-major order. +const fn coord_to_idx(x: u32, y: u32, xsize: u32) -> usize { + (y * xsize + x) as usize +} + +/// Compute the error-adjusted new lab value based on the error value of the currently scanned +/// pixel, plus a scalar factor. +fn get_error_adjusted(orig: &Lab, err: &Lab, scalar: f32) -> Lab { + let (p_l, p_a, p_b) = orig.into_components(); + let (err_l, err_a, err_b) = err.into_components(); + Lab::from_components(( + p_l + err_l * scalar, + p_a + err_a * scalar, + p_b + err_b * scalar, + )) +} + +struct DiffusionPoint { + xshift: i32, + yshift: i32, + scale: f32, +} + +impl DiffusionPoint { + const fn new(xshift: i32, yshift: i32, scale: f32) -> Self { + Self { + xshift, + yshift, + scale, + } + } +} + +const FLOYD_STEINBERG: [DiffusionPoint; 4] = [ + DiffusionPoint::new(1, 0, 7.0 / 16.0), + DiffusionPoint::new(-1, 1, 3.0 / 16.0), + DiffusionPoint::new(0, 1, 5.0 / 16.0), + DiffusionPoint::new(1, 1, 1.0 / 16.0), +]; + 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(); + let (xsize, ysize) = img.dimensions(); + let mut temp_img: Vec = Vec::with_capacity((xsize * ysize) as usize); for pix in srgb { temp_img.push(pix.into_format().into_color()); } + // now we take our units. + + for y in 0..ysize { + for x in 0..xsize { + let index = coord_to_idx(x, y, xsize); + let curr_pix = temp_img[index]; + let (nearest, err) = nearest_neighbor(curr_pix); + // set the color in the output buffer. + output.0[index] = nearest; + // take the error, and propagate it. + for point in FLOYD_STEINBERG { + let Some(target_x) = x.checked_add_signed(point.xshift) else { + continue; + }; + let Some(target_y) = y.checked_add_signed(point.yshift) else { + continue; + }; + let target = coord_to_idx( + target_x, + target_y, + xsize, + ); + if let Some(pix) = temp_img.get(target) { + temp_img[target] = get_error_adjusted(pix, &err, point.scale); + } + } + } + } } } diff --git a/src/main.rs b/src/main.rs index 1d031d1..3dce30d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ pub mod display; pub mod imageproc; use crate::display::Wrapper; -use crate::imageproc::{Ditherer, EInkBuffer, NNDither}; +use crate::imageproc::{Ditherer, EInkBuffer, FloydSteinbergDither, NNDither}; use clap::{Parser, Subcommand}; use image::RgbImage; @@ -19,25 +19,47 @@ struct Cli { enum Command { /// Load a single image Show, + /// Display a test pattern + Test, /// Start the HTTP sever Serve, + /// Convert an image and save it. + Convert, } fn main() -> anyhow::Result<()> { let cli = Cli::parse(); println!("CLI {cli:?}"); - if let Command::Show = cli.command { + if matches!(cli.command, Command::Show) { let img: RgbImage = image::io::Reader::open("myimage.png")?.decode()?.into(); let mut display = Wrapper::new()?; let mut eink_buf = EInkBuffer::new(800, 480); - let dither = NNDither {}; + let mut dither = NNDither{}; dither.dither(&img, &mut eink_buf); let raw_buf = eink_buf.into_display_buffer(); display.display(&raw_buf)?; + } + if matches!(cli.command, Command::Test) { + let mut display = Wrapper::new()?; + + display.test()?; + } + + if matches!(cli.command, Command::Convert) { + let img: RgbImage = image::io::Reader::open("myimage.png")?.decode()?.into(); + let mut eink_buf = EInkBuffer::new(800, 480); + let mut dither = FloydSteinbergDither{}; + + dither.dither(&img, &mut eink_buf); + let raw_buf = eink_buf.into_display_buffer(); + + } + + Ok(()) }