diff --git a/src/api.rs b/src/api.rs index f871e0b..6a58820 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,5 +1,5 @@ use crate::display::EInkPanel; -use crate::imageproc::{DitherMethod, DitherPalette, EInkImage}; +use crate::imageproc::{DitherMethod, DitherPalette, DitheredImage}; use axum::async_trait; use axum::extract::{FromRequest, Multipart, State}; use axum::http::{header, StatusCode}; @@ -13,7 +13,7 @@ use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio::task::JoinHandle; -use tracing::{debug, error, info, instrument}; +use tracing::{error, info, instrument}; #[derive(thiserror::Error, Debug)] pub enum ApiError { @@ -66,7 +66,7 @@ where #[derive(Debug)] pub struct DisplaySetCommand { - img: Box, + img: Box, } #[instrument(skip_all)] @@ -134,15 +134,16 @@ where _ => {} } } - if let Some(i) = img { - Ok(Self { - image: i, - dither_method: dither_method.unwrap_or(DitherMethod::NearestNeighbor), - palette: palette.unwrap_or(DitherPalette::Default), - }) - } else { - Err(ApiError::MissingImage.into()) - } + img.map_or_else( + || Err(ApiError::MissingImage.into()), + |i| { + Ok(Self { + image: i, + dither_method: dither_method.unwrap_or(DitherMethod::NearestNeighbor), + palette: palette.unwrap_or(DitherPalette::Default), + }) + }, + ) } } @@ -151,7 +152,8 @@ async fn set_image( State(ctx): State, img_req: ImageRequest, ) -> Result { - let mut buf = EInkImage::new(img_req.palette.value().to_vec()); + // FIXME: resize image to 800x480 to match the eink panel. + let mut buf = DitheredImage::new(img_req.image.width(), img_req.image.height(), img_req.palette.value().to_vec()); { let mut dither = img_req.dither_method.get_ditherer(); dither.dither(&img_req.image, &mut buf); @@ -166,7 +168,7 @@ async fn set_image( /// generates a dithered image based on the given image and the dithering parameters. /// Can be used to see how the dithering and palette choices affect the result. async fn preview_image(img_req: ImageRequest) -> Result { - let mut buf = EInkImage::new(img_req.palette.value().to_vec()); + let mut buf = DitheredImage::new(img_req.image.width(), img_req.image.height(), img_req.palette.value().to_vec()); { let mut dither = img_req.dither_method.get_ditherer(); dither.dither(&img_req.image, &mut buf); diff --git a/src/display.rs b/src/display.rs index 72898b0..08cc5b8 100644 --- a/src/display.rs +++ b/src/display.rs @@ -8,10 +8,10 @@ use tracing::{debug, warn}; use anyhow::Result; use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags}; -use crate::imageproc::EInkImage; +use crate::imageproc::DitheredImage; pub trait EInkPanel { - fn display(&mut self, buf: &EInkImage) -> Result<()>; + fn display(&mut self, buf: &DitheredImage) -> Result<()>; } pub struct Wrapper { @@ -66,7 +66,7 @@ impl Wrapper { impl EInkPanel for Wrapper { #[instrument(skip_all)] - fn display(&mut self, img: &EInkImage) -> Result<()> { + fn display(&mut self, img: &DitheredImage) -> Result<()> { let buf = img.into_display_buffer(); self.panel .update_and_display_frame(&mut self.spi, &buf, &mut self.delay)?; @@ -77,13 +77,11 @@ impl EInkPanel for Wrapper { } } - /// A Fake EInk display for testing purposes. /// Saves the output as `display.bmp` pub struct FakeEInk(); impl EInkPanel for FakeEInk { - fn display(&mut self, img: &EInkImage) -> Result<()> { - + fn display(&mut self, img: &DitheredImage) -> Result<()> { warn!("Fake display was called: saving to display.bmp"); img.into_rgbimage().save("display.bmp"); diff --git a/src/imageproc.rs b/src/imageproc.rs index 302359d..40da8fc 100644 --- a/src/imageproc.rs +++ b/src/imageproc.rs @@ -75,12 +75,12 @@ pub enum ProcessingError { /// Buffer to be sent to the ``EInk`` display. #[derive(Debug)] -pub struct EInkImage { +pub struct DitheredImage { buf: ImageBuffer, Vec>, palette: Vec, } -impl EInkImage { +impl DitheredImage { #[must_use] pub fn into_display_buffer(&self) -> Vec { let mut buf = Vec::with_capacity(self.buf.len() / 2); @@ -90,7 +90,7 @@ impl EInkImage { buf } - /// Convert the EInk-palette image into an RGB image to be viewed on a regular screen. + /// Convert the index-based image back into an RGB image using the color palette. #[must_use] pub fn into_rgbimage(&self) -> RgbImage { RgbImage::from_fn(self.buf.width(), self.buf.height(), |x, y| { @@ -104,25 +104,25 @@ impl EInkImage { }) } - /// Constructs a new EInk Image based on the given color palette for + /// Constructs a new Dithered Image based on the given dimensions and color palette for /// color indexing. #[must_use] - pub fn new(palette: Vec) -> Self { + pub fn new(width: u32, height: u32, palette: Vec) -> Self { Self { - buf: GrayImage::new(800, 480), + buf: GrayImage::new(width, height), palette, } } } -impl Default for EInkImage { +impl Default for DitheredImage { fn default() -> Self { - Self::new(DISPLAY_PALETTE.to_vec()) + Self::new(800, 480, DISPLAY_PALETTE.to_vec()) } } pub trait Ditherer { - fn dither(&mut self, img: &RgbImage, output: &mut EInkImage); + fn dither(&mut self, img: &RgbImage, output: &mut DitheredImage); } /// Find the closest approximate palette color to the given sRGB value. @@ -143,7 +143,7 @@ fn nearest_neighbor(input_color: Lab, palette: &[Srgb]) -> (u8, Lab) { pub struct NNDither(); impl Ditherer for NNDither { - fn dither(&mut self, img: &RgbImage, output: &mut EInkImage) { + fn dither(&mut self, img: &RgbImage, output: &mut DitheredImage) { assert!(img.width() == 800); assert!(img.height() == 480); @@ -253,7 +253,7 @@ impl<'a> ErrorDiffusionDither<'a> { impl<'a> Ditherer for ErrorDiffusionDither<'a> { #[instrument] - fn dither(&mut self, img: &RgbImage, output: &mut EInkImage) { + fn dither(&mut self, img: &RgbImage, output: &mut DitheredImage) { // create a copy of the image in Lab space, mutable. // first, a view into the rgb components let srgb = <&[Srgb]>::from_components(&**img); diff --git a/src/main.rs b/src/main.rs index 8db90dc..4156b70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ pub mod errors; pub mod imageproc; use crate::display::{EInkPanel, FakeEInk, Wrapper}; -use crate::imageproc::{DitherMethod, EInkImage}; +use crate::imageproc::{DitherMethod, DitheredImage}; use clap::{Parser, Subcommand}; use image::RgbImage; use tracing::{error, info}; @@ -38,7 +38,7 @@ async fn main() -> anyhow::Result<()> { error!("HI"); let mut display = FakeEInk {}; - let mut eink_buf = EInkImage::default(); + let mut eink_buf = DitheredImage::default(); let mut dither = DitherMethod::Atkinson.get_ditherer(); dither.dither(&img, &mut eink_buf); @@ -51,7 +51,7 @@ async fn main() -> anyhow::Result<()> { } if matches!(cli.command, Command::Serve) { - let display = Wrapper::new()?; + let display = FakeEInk {}; let ctx = api::Context::new(Box::new(display));