supprt multiple dithering matrix options
This commit is contained in:
parent
8417562e31
commit
0a7668b0a7
118
src/imageproc.rs
118
src/imageproc.rs
|
@ -1,7 +1,7 @@
|
|||
use image::RgbImage;
|
||||
|
||||
use palette::{cast::FromComponents, IntoColor, Lab, Oklch, Srgb};
|
||||
use palette::color_difference::{Ciede2000, EuclideanDistance};
|
||||
use palette::{cast::FromComponents, IntoColor, Lab, Oklch, Srgb};
|
||||
|
||||
/// Palette used on the display; pixels can be one of these colors.
|
||||
///
|
||||
|
@ -16,18 +16,7 @@ const DISPLAY_PALETTE: [Srgb; 7] = [
|
|||
Srgb::new(0.757, 0.443, 0.165), // Orange
|
||||
];
|
||||
|
||||
// fn octcolor_rgb(color: &OctColor) -> &Srgb<u8> {
|
||||
// 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],
|
||||
// }
|
||||
// }
|
||||
// TODO: support different color palettes.
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DisplayColor {
|
||||
|
@ -121,17 +110,9 @@ impl EInkBuffer {
|
|||
// }
|
||||
|
||||
pub trait Ditherer {
|
||||
fn dither(&mut self, img: &RgbImage, output: &mut EInkBuffer);
|
||||
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.
|
||||
#[must_use]
|
||||
|
@ -151,7 +132,7 @@ pub fn nearest_neighbor(input_color: Lab) -> (DisplayColor, Lab) {
|
|||
pub struct NNDither();
|
||||
|
||||
impl Ditherer for NNDither {
|
||||
fn dither(&mut self, img: &RgbImage, output: &mut EInkBuffer) {
|
||||
fn dither(&self, img: &RgbImage, output: &mut EInkBuffer) {
|
||||
assert!(img.width() == 800);
|
||||
assert!(img.height() == 480);
|
||||
|
||||
|
@ -165,8 +146,6 @@ impl Ditherer for NNDither {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct FloydSteinbergDither();
|
||||
|
||||
/// Compute the vector index for a given image by using the size of rows. Assumes that images
|
||||
/// are indexed in row-major order.
|
||||
const fn coord_to_idx(x: u32, y: u32, xsize: u32) -> usize {
|
||||
|
@ -174,7 +153,7 @@ const fn coord_to_idx(x: u32, y: u32, xsize: u32) -> usize {
|
|||
}
|
||||
|
||||
/// Compute the error-adjusted new lab value based on the error value of the currently scanned
|
||||
/// pixel, plus a scalar factor.
|
||||
/// pixel multiplied by a scalar factor.
|
||||
fn get_error_adjusted(orig: &Lab, err: &Lab, scalar: f32) -> Lab {
|
||||
let (p_l, p_a, p_b) = orig.into_components();
|
||||
let (err_l, err_a, err_b) = err.into_components();
|
||||
|
@ -185,6 +164,9 @@ fn get_error_adjusted(orig: &Lab, err: &Lab, scalar: f32) -> Lab {
|
|||
))
|
||||
}
|
||||
|
||||
|
||||
/// ``DiffusionPoint`` is part of the diffusion matrix, represented by a shift in x and y and an error
|
||||
/// scaling factor.
|
||||
struct DiffusionPoint {
|
||||
xshift: i32,
|
||||
yshift: i32,
|
||||
|
@ -192,6 +174,7 @@ struct DiffusionPoint {
|
|||
}
|
||||
|
||||
impl DiffusionPoint {
|
||||
/// Creates a new ``DiffusionPoint``
|
||||
const fn new(xshift: i32, yshift: i32, scale: f32) -> Self {
|
||||
Self {
|
||||
xshift,
|
||||
|
@ -201,23 +184,78 @@ impl DiffusionPoint {
|
|||
}
|
||||
}
|
||||
|
||||
const FLOYD_STEINBERG: [DiffusionPoint; 4] = [
|
||||
static FLOYD_STEINBERG_POINTS: &[DiffusionPoint] = &[
|
||||
DiffusionPoint::new(1, 0, 7.0 / 16.0),
|
||||
DiffusionPoint::new(-1, 1, 3.0 / 16.0),
|
||||
DiffusionPoint::new(0, 1, 5.0 / 16.0),
|
||||
DiffusionPoint::new(1, 1, 1.0 / 16.0),
|
||||
];
|
||||
const ATKINSON_DITHER: [DiffusionPoint; 6] = [
|
||||
DiffusionPoint::new(1, 0, 1.0/8.0),
|
||||
DiffusionPoint::new(2, 0, 1.0/8.0),
|
||||
DiffusionPoint::new(-1, 1, 1.0/8.0),
|
||||
DiffusionPoint::new(0, 1, 1.0/8.0),
|
||||
DiffusionPoint::new(1, 1, 1.0/8.0),
|
||||
DiffusionPoint::new(0, 2, 1.0/8.0),
|
||||
|
||||
static ATKINSON_DITHER_POINTS: &[DiffusionPoint] = &[
|
||||
DiffusionPoint::new(1, 0, 1.0 / 8.0),
|
||||
DiffusionPoint::new(2, 0, 1.0 / 8.0),
|
||||
DiffusionPoint::new(-1, 1, 1.0 / 8.0),
|
||||
DiffusionPoint::new(0, 1, 1.0 / 8.0),
|
||||
DiffusionPoint::new(1, 1, 1.0 / 8.0),
|
||||
DiffusionPoint::new(0, 2, 1.0 / 8.0),
|
||||
];
|
||||
|
||||
impl Ditherer for FloydSteinbergDither {
|
||||
fn dither(&mut self, img: &RgbImage, output: &mut EInkBuffer) {
|
||||
static SIERRA_DITHER_POINTS: &[DiffusionPoint] = &[
|
||||
DiffusionPoint::new(1, 0, 5.0 / 32.0),
|
||||
DiffusionPoint::new(2, 0, 3.0 / 32.0),
|
||||
DiffusionPoint::new(-2, 1, 2.0 / 32.0),
|
||||
DiffusionPoint::new(-1, 1, 4.0 / 32.0),
|
||||
DiffusionPoint::new(0, 1, 5.0 / 32.0),
|
||||
DiffusionPoint::new(1, 1, 4.0 / 32.0),
|
||||
DiffusionPoint::new(2, 1, 2.0 / 32.0),
|
||||
DiffusionPoint::new(-1, 2, 2.0 / 32.0),
|
||||
DiffusionPoint::new(0, 2, 3.0 / 32.0),
|
||||
DiffusionPoint::new(1, 2, 2.0 / 32.0),
|
||||
];
|
||||
|
||||
static STUKI_DITHER_POINTS: &[DiffusionPoint] = &[
|
||||
DiffusionPoint::new(1, 0, 8.0 / 42.0),
|
||||
DiffusionPoint::new(2, 0, 4.0 / 42.0),
|
||||
DiffusionPoint::new(-2, 1, 2.0 / 42.0),
|
||||
DiffusionPoint::new(-1, 1, 4.0 / 42.0),
|
||||
DiffusionPoint::new(0, 1, 8.0 / 42.0),
|
||||
DiffusionPoint::new(1, 1, 4.0 / 42.0),
|
||||
DiffusionPoint::new(2, 1, 2.0 / 42.0),
|
||||
DiffusionPoint::new(-2, 2, 1.0 / 42.0),
|
||||
DiffusionPoint::new(-1, 2, 2.0 / 42.0),
|
||||
DiffusionPoint::new(0, 2, 4.0 / 42.0),
|
||||
DiffusionPoint::new(1, 2, 2.0 / 42.0),
|
||||
DiffusionPoint::new(1, 2, 1.0 / 42.0),
|
||||
];
|
||||
|
||||
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 struct ErrorDiffusionDither(DiffusionMatrix);
|
||||
impl ErrorDiffusionDither {
|
||||
#[must_use]
|
||||
pub const fn new(dm: DiffusionMatrix) -> Self {
|
||||
Self(dm)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ditherer for ErrorDiffusionDither {
|
||||
fn dither(&self, img: &RgbImage, output: &mut EInkBuffer) {
|
||||
// create a copy of the image in Lab space, mutable.
|
||||
let srgb = <&[Srgb<u8>]>::from_components(&**img);
|
||||
let (xsize, ysize) = img.dimensions();
|
||||
|
@ -235,18 +273,14 @@ impl Ditherer for FloydSteinbergDither {
|
|||
// set the color in the output buffer.
|
||||
output.0[index] = nearest;
|
||||
// take the error, and propagate it.
|
||||
for point in ATKINSON_DITHER {
|
||||
for point in self.0.value() {
|
||||
let Some(target_x) = x.checked_add_signed(point.xshift) else {
|
||||
continue;
|
||||
};
|
||||
let Some(target_y) = y.checked_add_signed(point.yshift) else {
|
||||
continue;
|
||||
};
|
||||
let target = coord_to_idx(
|
||||
target_x,
|
||||
target_y,
|
||||
xsize,
|
||||
);
|
||||
let target = coord_to_idx(target_x, target_y, xsize);
|
||||
if let Some(pix) = temp_img.get(target) {
|
||||
temp_img[target] = get_error_adjusted(pix, &err, point.scale);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ pub mod display;
|
|||
pub mod imageproc;
|
||||
|
||||
use crate::display::Wrapper;
|
||||
use crate::imageproc::{Ditherer, EInkBuffer, FloydSteinbergDither, NNDither};
|
||||
use crate::imageproc::{DiffusionMatrix, EInkBuffer, ErrorDiffusionDither, Ditherer};
|
||||
use clap::{Parser, Subcommand};
|
||||
use image::RgbImage;
|
||||
|
||||
|
@ -36,7 +36,7 @@ fn main() -> anyhow::Result<()> {
|
|||
let mut display = Wrapper::new()?;
|
||||
|
||||
let mut eink_buf = EInkBuffer::new(800, 480);
|
||||
let mut dither = FloydSteinbergDither{};
|
||||
let dither = ErrorDiffusionDither::new(DiffusionMatrix::Atkinson);
|
||||
|
||||
dither.dither(&img, &mut eink_buf);
|
||||
let raw_buf = eink_buf.into_display_buffer();
|
||||
|
@ -52,7 +52,7 @@ fn main() -> anyhow::Result<()> {
|
|||
if matches!(cli.command, Command::Convert) {
|
||||
let img: RgbImage = image::io::Reader::open("myimage.png")?.decode()?.into();
|
||||
let mut eink_buf = EInkBuffer::new(800, 480);
|
||||
let mut dither = FloydSteinbergDither{};
|
||||
let dither = ErrorDiffusionDither::new(DiffusionMatrix::Atkinson);
|
||||
|
||||
dither.dither(&img, &mut eink_buf);
|
||||
let raw_buf = eink_buf.into_display_buffer();
|
||||
|
|
Loading…
Reference in a new issue