use image::RgbImage; use palette::{cast::FromComponents, color_difference::Ciede2000, 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 ]; // 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 From for Srgb { fn from(value: DisplayColor) -> Self { DISPLAY_PALETTE[value 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), } } fn into_byte(color1: Self, color2: Self) -> u8 { let upper: u8 = color1.into(); let lower: u8 = color2.into(); upper << 4 | lower } } impl From for u8 { fn from(value: DisplayColor) -> Self { value as u8 } } /// Buffer to be sent to the EInk display. #[derive(Debug)] pub struct EInkBuffer(Vec); impl EInkBuffer { pub fn into_display_buffer(&self) -> Vec { 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])); } buf } pub fn new(width: usize, height: usize) -> Self { let v = vec![DisplayColor::Black; width * height]; EInkBuffer(v) } } // 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 fn set(&mut self, x: usize, y: usize, value: DisplayColor) { // self.data[x + y * self.width] = value; // } // pub fn get(&self, x:usize, y:usize) -> DisplayColor { // self.data[x // } // } pub trait Ditherer { fn dither(&self, img: &RgbImage, output: &mut 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 input: Lab = color.into_color(); let (nearest, dist) = DISPLAY_PALETTE .iter() .enumerate() .map(|(idx, p_color)| { let c: Lab = (*p_color).into_color(); (idx, input.difference(c)) }) .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, 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()); output.0[idx] = n; } } }