wip: use DitherMethod instead of individual ditherers

This commit is contained in:
saji 2024-07-29 16:39:49 -05:00
parent 608ebe9ec5
commit 624ad1f101
5 changed files with 52 additions and 39 deletions

1
Cargo.lock generated
View file

@ -167,6 +167,7 @@ checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core", "axum-core",
"axum-macros",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",

View file

@ -7,7 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.86" anyhow = "1.0.86"
axum = { version = "0.7.5", features = ["multipart"] } axum = { version = "0.7.5", features = ["macros", "multipart"] }
axum-macros = "0.4.1" axum-macros = "0.4.1"
clap = { version = "4.5.7", features = ["derive"] } clap = { version = "4.5.7", features = ["derive"] }
epd-waveshare = { git = "https://github.com/caemor/epd-waveshare.git"} epd-waveshare = { git = "https://github.com/caemor/epd-waveshare.git"}

View file

@ -1,13 +1,12 @@
use crate::imageproc::{DiffusionMatrix, Ditherer, EInkImage, ErrorDiffusionDither}; use crate::imageproc::{DitherMethod, EInkImage, };
use axum::extract::Multipart; use axum::extract::Multipart;
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::{extract::State, response::Response, routing::post, Router}; use axum::{extract::State, response::Response, routing::post, Router};
use image::ImageReader; use image::{DynamicImage, ImageReader};
use std::io::Cursor; use std::io::Cursor;
use std::time::Duration; use std::time::Duration;
use tracing::{debug, error, info, instrument}; use tracing::{debug, error, info, instrument};
use serde::{Serialize, Deserialize};
use crate::display::EInkPanel; use crate::display::EInkPanel;
use std::sync::Arc; use std::sync::Arc;
@ -90,13 +89,14 @@ pub fn router() -> Router<Context> {
.route("/process_image", post(process_image)) .route("/process_image", post(process_image))
} }
#[derive(Debug)]
#[derive(Serialize, Deserialize)]
pub struct ImageRequest { pub struct ImageRequest {
image: Box<DynamicImage>,
} }
#[instrument(skip(ctx))] #[instrument(skip(ctx))]
#[axum::debug_handler]
async fn set_image( async fn set_image(
State(ctx): State<Context>, State(ctx): State<Context>,
mut parts: Multipart, mut parts: Multipart,
@ -111,14 +111,15 @@ async fn set_image(
.expect("Cursor io never fails"); .expect("Cursor io never fails");
debug!("Guessed format: {:?}", reader.format()); debug!("Guessed format: {:?}", reader.format());
let image = reader.decode()?;
let mut buf = EInkImage::default(); let mut buf = EInkImage::default();
let mut dither = ErrorDiffusionDither::new(DiffusionMatrix::Atkinson); {
dither.dither(&image.into(), &mut buf); let image = reader.decode()?;
let mut dither = DitherMethod::Atkinson.get_ditherer();
dither.dither(&image.into(), &mut buf);
}
let cmd = DisplaySetCommand { img: Box::new(buf) }; let cmd = DisplaySetCommand { img: Box::new(buf) };
ctx.display_channel ctx.display_channel
.send_timeout(cmd, Duration::from_secs(10)) .send_timeout(cmd, Duration::from_secs(10)).await?;
.await?;
} }
} }
Ok(()) Ok(())

View file

@ -1,5 +1,6 @@
use image::{GrayImage, ImageBuffer, Luma, RgbImage}; use image::{GrayImage, ImageBuffer, Luma, RgbImage};
use palette::FromColor; use palette::FromColor;
use serde::{Deserialize, Serialize};
use tracing::instrument; use tracing::instrument;
use image::Rgb as imgRgb; use image::Rgb as imgRgb;
@ -19,6 +20,30 @@ const DISPLAY_PALETTE: [Srgb; 7] = [
Srgb::new(0.757, 0.443, 0.165), // Orange Srgb::new(0.757, 0.443, 0.165), // Orange
]; ];
pub enum DitherPalette {}
#[derive(strum::EnumString, Serialize, Deserialize, PartialEq, Eq)]
pub enum DitherMethod {
NearestNeighbor,
FloydSteinberg,
Atkinson,
Stuki,
Sierra,
}
impl DitherMethod {
#[must_use]
pub fn get_ditherer(&self) -> Box<dyn Ditherer> {
match self {
Self::NearestNeighbor => Box::new(NNDither {}),
Self::Atkinson => Box::new(ErrorDiffusionDither::new(ATKINSON_DITHER_POINTS)),
Self::FloydSteinberg => Box::new(ErrorDiffusionDither::new(FLOYD_STEINBERG_POINTS)),
Self::Stuki => Box::new(ErrorDiffusionDither::new(STUKI_DITHER_POINTS)),
Self::Sierra => Box::new(ErrorDiffusionDither::new(SIERRA_DITHER_POINTS)),
}
}
}
pub enum ProcessingError { pub enum ProcessingError {
DitherError, DitherError,
PaletteIndexError(usize), PaletteIndexError(usize),
@ -125,6 +150,7 @@ fn compute_error_adjusted_color(orig: &Lab, err: &Lab, weight: f32) -> Lab {
/// ``DiffusionPoint`` is part of the diffusion matrix, represented by a shift in x and y and an error /// ``DiffusionPoint`` is part of the diffusion matrix, represented by a shift in x and y and an error
/// scaling factor. /// scaling factor.
#[derive(Debug)]
struct DiffusionPoint { struct DiffusionPoint {
xshift: i32, xshift: i32,
yshift: i32, yshift: i32,
@ -186,35 +212,19 @@ static STUKI_DITHER_POINTS: &[DiffusionPoint] = &[
DiffusionPoint::new(1, 2, 1.0 / 42.0), DiffusionPoint::new(1, 2, 1.0 / 42.0),
]; ];
#[derive(Debug)] pub type DiffusionMatrix<'a> = &'a [DiffusionPoint];
pub enum DiffusionMatrix {
FloydSteinberg,
Atkinson,
Sierra,
Stuki,
}
impl DiffusionMatrix {
fn value(&self) -> &'static [DiffusionPoint] {
match *self {
Self::FloydSteinberg => FLOYD_STEINBERG_POINTS,
Self::Atkinson => ATKINSON_DITHER_POINTS,
Self::Sierra => SIERRA_DITHER_POINTS,
Self::Stuki => STUKI_DITHER_POINTS,
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct ErrorDiffusionDither(DiffusionMatrix); pub struct ErrorDiffusionDither<'a>(&'a [DiffusionPoint]);
impl ErrorDiffusionDither {
impl<'a> ErrorDiffusionDither<'a> {
#[must_use] #[must_use]
pub const fn new(dm: DiffusionMatrix) -> Self { pub const fn new(dm: DiffusionMatrix<'a>) -> Self {
Self(dm) Self(dm)
} }
} }
impl Ditherer for ErrorDiffusionDither { impl<'a> Ditherer for ErrorDiffusionDither<'a> {
#[instrument] #[instrument]
fn dither(&mut self, img: &RgbImage, output: &mut EInkImage) { fn dither(&mut self, img: &RgbImage, output: &mut EInkImage) {
// create a copy of the image in Lab space, mutable. // create a copy of the image in Lab space, mutable.
@ -226,8 +236,8 @@ impl Ditherer for ErrorDiffusionDither {
for pix in srgb { for pix in srgb {
temp_img.push(pix.into_format().into_color()); temp_img.push(pix.into_format().into_color());
} }
// now we take our units.
// TODO: rework this to make more sense.
for y in 0..ysize { for y in 0..ysize {
for x in 0..xsize { for x in 0..xsize {
let index = coord_to_idx(x, y, xsize); let index = coord_to_idx(x, y, xsize);
@ -236,7 +246,8 @@ impl Ditherer for ErrorDiffusionDither {
// set the color in the output buffer. // set the color in the output buffer.
*output.buf.get_mut(index).unwrap() = nearest; *output.buf.get_mut(index).unwrap() = nearest;
// take the error, and propagate it. // take the error, and propagate it.
for point in self.0.value() { for point in self.0 {
// bounds checking.
let Some(target_x) = x.checked_add_signed(point.xshift) else { let Some(target_x) = x.checked_add_signed(point.xshift) else {
continue; continue;
}; };

View file

@ -3,8 +3,8 @@ pub mod display;
pub mod errors; pub mod errors;
pub mod imageproc; pub mod imageproc;
use crate::display::{FakeEInk, EInkPanel, Wrapper}; use crate::display::{EInkPanel, FakeEInk, Wrapper};
use crate::imageproc::{DiffusionMatrix, Ditherer, EInkImage, ErrorDiffusionDither}; use crate::imageproc::{DitherMethod, EInkImage};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use image::RgbImage; use image::RgbImage;
use tracing::{error, info}; use tracing::{error, info};
@ -39,7 +39,7 @@ async fn main() -> anyhow::Result<()> {
let mut display = FakeEInk {}; let mut display = FakeEInk {};
let mut eink_buf = EInkImage::default(); let mut eink_buf = EInkImage::default();
let mut dither = ErrorDiffusionDither::new(DiffusionMatrix::Atkinson); let mut dither = DitherMethod::Atkinson.get_ditherer();
dither.dither(&img, &mut eink_buf); dither.dither(&img, &mut eink_buf);
display.display(&eink_buf)?; display.display(&eink_buf)?;