use image::ColorType::Rgb8; use image::RgbImage; use palette::{cast::FromComponents, color_difference::EuclideanDistance, FromColor, IntoColor, Oklab, 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 ]; // fn octcolor_rgb(color: &OctColor) -> &Srgb { // match color { // OctColor::Black => &DISPLAY_PALETTE[0], // OctColor::White => &DISPLAY_PALETTE[1], // OctColor::Green => &DISPLAY_PALETTE[2], // OctColor::Blue => &DISPLAY_PALETTE[3], // OctColor::Red => &DISPLAY_PALETTE[4], // OctColor::Yellow => &DISPLAY_PALETTE[5], // OctColor::Orange => &DISPLAY_PALETTE[6], // OctColor::HiZ => &DISPLAY_PALETTE[1], // } // } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DisplayColor { Black, White, Green, Blue, Red, Yellow, Orange, } impl Into for DisplayColor { fn into(self) -> Srgb { DISPLAY_PALETTE[self as usize] } } impl DisplayColor { fn from_u8(value: u8) -> Self { match value { 0 => Self::Black, 1 => Self::White, 2 => Self::Green, 3 => Self::Blue, 4 => Self::Red, 5 => Self::Yellow, 6 => Self::Orange, _ => panic!("unexpected DisplayColor {}", value), } } } /// Buffer to be sent to the EInk display. #[derive(Debug)] pub struct EInkBuffer { data: Vec, width: usize, height: usize, } impl EInkBuffer { /// Converts the EInkBuffer into data that can be sent over the SPI API /// Bin-packs the two 4-bit colors into bytes. pub fn into_buffer(&self) -> Vec { vec![] } pub fn new(width: usize, height: usize) -> EInkBuffer { EInkBuffer { data: vec![DisplayColor::Black; width * height], width, height, } } } pub trait Ditherer { fn dither(&self, img: &RgbImage) -> EInkBuffer; } // fn color_distance(c1: LinSrgb, c2: LinSrgb) -> f32 { // let r2 = (c2.red - c1.red).powf(2.0); // let g2 = (c2.green - c1.green).powf(2.0); // let b2 = (c2.blue - c1.blue).powf(2.0); // // (r2 + g2 + b2).sqrt() // } /// 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 (nearest, dist) = DISPLAY_PALETTE .iter() .enumerate() .map(|(idx, p_color)| { let lab: Oklab = p_color.clone().into_color(); (idx, color.distance(p_color.into_format())) }) .min_by(|(_, a), (_, b)| a.total_cmp(b)) .unwrap(); (DisplayColor::from_u8(nearest as u8), dist) } pub struct NNDither(); impl Ditherer for NNDither { fn dither(&self, img: &RgbImage) -> EInkBuffer { assert!(img.width() == 800); assert!(img.height() == 480); let mut buf = EInkBuffer::new(800, 480); // img.enumerate_pixels().for_each(|(x, y, pix)| { // buf[x + y * 800] = nearest_neighbor(Srgb::from_components()); // }); let srgb = <&[Srgb]>::from_components(&**img); buf } }