wip: use DitherMethod instead of individual ditherers
This commit is contained in:
parent
608ebe9ec5
commit
624ad1f101
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"}
|
||||||
|
|
23
src/api.rs
23
src/api.rs
|
@ -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(())
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
Loading…
Reference in a new issue