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 = [
"async-trait",
"axum-core",
"axum-macros",
"bytes",
"futures-util",
"http",

View file

@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0.86"
axum = { version = "0.7.5", features = ["multipart"] }
axum = { version = "0.7.5", features = ["macros", "multipart"] }
axum-macros = "0.4.1"
clap = { version = "4.5.7", features = ["derive"] }
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::http::StatusCode;
use axum::response::IntoResponse;
use axum::{extract::State, response::Response, routing::post, Router};
use image::ImageReader;
use image::{DynamicImage, ImageReader};
use std::io::Cursor;
use std::time::Duration;
use tracing::{debug, error, info, instrument};
use serde::{Serialize, Deserialize};
use crate::display::EInkPanel;
use std::sync::Arc;
@ -90,13 +89,14 @@ pub fn router() -> Router<Context> {
.route("/process_image", post(process_image))
}
#[derive(Serialize, Deserialize)]
#[derive(Debug)]
pub struct ImageRequest {
image: Box<DynamicImage>,
}
#[instrument(skip(ctx))]
#[axum::debug_handler]
async fn set_image(
State(ctx): State<Context>,
mut parts: Multipart,
@ -111,14 +111,15 @@ async fn set_image(
.expect("Cursor io never fails");
debug!("Guessed format: {:?}", reader.format());
let image = reader.decode()?;
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) };
ctx.display_channel
.send_timeout(cmd, Duration::from_secs(10))
.await?;
.send_timeout(cmd, Duration::from_secs(10)).await?;
}
}
Ok(())

View file

@ -1,5 +1,6 @@
use image::{GrayImage, ImageBuffer, Luma, RgbImage};
use palette::FromColor;
use serde::{Deserialize, Serialize};
use tracing::instrument;
use image::Rgb as imgRgb;
@ -19,6 +20,30 @@ const DISPLAY_PALETTE: [Srgb; 7] = [
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 {
DitherError,
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
/// scaling factor.
#[derive(Debug)]
struct DiffusionPoint {
xshift: i32,
yshift: i32,
@ -186,35 +212,19 @@ static STUKI_DITHER_POINTS: &[DiffusionPoint] = &[
DiffusionPoint::new(1, 2, 1.0 / 42.0),
];
#[derive(Debug)]
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,
}
}
}
pub type DiffusionMatrix<'a> = &'a [DiffusionPoint];
#[derive(Debug)]
pub struct ErrorDiffusionDither(DiffusionMatrix);
impl ErrorDiffusionDither {
pub struct ErrorDiffusionDither<'a>(&'a [DiffusionPoint]);
impl<'a> ErrorDiffusionDither<'a> {
#[must_use]
pub const fn new(dm: DiffusionMatrix) -> Self {
pub const fn new(dm: DiffusionMatrix<'a>) -> Self {
Self(dm)
}
}
impl Ditherer for ErrorDiffusionDither {
impl<'a> Ditherer for ErrorDiffusionDither<'a> {
#[instrument]
fn dither(&mut self, img: &RgbImage, output: &mut EInkImage) {
// create a copy of the image in Lab space, mutable.
@ -226,8 +236,8 @@ impl Ditherer for ErrorDiffusionDither {
for pix in srgb {
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 x in 0..xsize {
let index = coord_to_idx(x, y, xsize);
@ -236,7 +246,8 @@ impl Ditherer for ErrorDiffusionDither {
// set the color in the output buffer.
*output.buf.get_mut(index).unwrap() = nearest;
// 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 {
continue;
};

View file

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