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 image::RgbImage;
|
||||||
|
|
||||||
use palette::{cast::FromComponents, IntoColor, Lab, Oklch, Srgb};
|
|
||||||
use palette::color_difference::{Ciede2000, EuclideanDistance};
|
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.
|
/// 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
|
Srgb::new(0.757, 0.443, 0.165), // Orange
|
||||||
];
|
];
|
||||||
|
|
||||||
// fn octcolor_rgb(color: &OctColor) -> &Srgb<u8> {
|
// TODO: support different color palettes.
|
||||||
// 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],
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum DisplayColor {
|
pub enum DisplayColor {
|
||||||
|
@ -121,17 +110,9 @@ impl EInkBuffer {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub trait Ditherer {
|
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.
|
/// Find the closest approximate palette color to the given sRGB value.
|
||||||
/// This uses euclidian distance in linear space.
|
/// This uses euclidian distance in linear space.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -151,7 +132,7 @@ pub fn nearest_neighbor(input_color: Lab) -> (DisplayColor, Lab) {
|
||||||
pub struct NNDither();
|
pub struct NNDither();
|
||||||
|
|
||||||
impl Ditherer for 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.width() == 800);
|
||||||
assert!(img.height() == 480);
|
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
|
/// Compute the vector index for a given image by using the size of rows. Assumes that images
|
||||||
/// are indexed in row-major order.
|
/// are indexed in row-major order.
|
||||||
const fn coord_to_idx(x: u32, y: u32, xsize: u32) -> usize {
|
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
|
/// 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 {
|
fn get_error_adjusted(orig: &Lab, err: &Lab, scalar: f32) -> Lab {
|
||||||
let (p_l, p_a, p_b) = orig.into_components();
|
let (p_l, p_a, p_b) = orig.into_components();
|
||||||
let (err_l, err_a, err_b) = err.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 {
|
struct DiffusionPoint {
|
||||||
xshift: i32,
|
xshift: i32,
|
||||||
yshift: i32,
|
yshift: i32,
|
||||||
|
@ -192,6 +174,7 @@ struct DiffusionPoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiffusionPoint {
|
impl DiffusionPoint {
|
||||||
|
/// Creates a new ``DiffusionPoint``
|
||||||
const fn new(xshift: i32, yshift: i32, scale: f32) -> Self {
|
const fn new(xshift: i32, yshift: i32, scale: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
xshift,
|
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, 0, 7.0 / 16.0),
|
||||||
DiffusionPoint::new(-1, 1, 3.0 / 16.0),
|
DiffusionPoint::new(-1, 1, 3.0 / 16.0),
|
||||||
DiffusionPoint::new(0, 1, 5.0 / 16.0),
|
DiffusionPoint::new(0, 1, 5.0 / 16.0),
|
||||||
DiffusionPoint::new(1, 1, 1.0 / 16.0),
|
DiffusionPoint::new(1, 1, 1.0 / 16.0),
|
||||||
];
|
];
|
||||||
const ATKINSON_DITHER: [DiffusionPoint; 6] = [
|
|
||||||
DiffusionPoint::new(1, 0, 1.0/8.0),
|
static ATKINSON_DITHER_POINTS: &[DiffusionPoint] = &[
|
||||||
DiffusionPoint::new(2, 0, 1.0/8.0),
|
DiffusionPoint::new(1, 0, 1.0 / 8.0),
|
||||||
DiffusionPoint::new(-1, 1, 1.0/8.0),
|
DiffusionPoint::new(2, 0, 1.0 / 8.0),
|
||||||
DiffusionPoint::new(0, 1, 1.0/8.0),
|
DiffusionPoint::new(-1, 1, 1.0 / 8.0),
|
||||||
DiffusionPoint::new(1, 1, 1.0/8.0),
|
DiffusionPoint::new(0, 1, 1.0 / 8.0),
|
||||||
DiffusionPoint::new(0, 2, 1.0/8.0),
|
DiffusionPoint::new(1, 1, 1.0 / 8.0),
|
||||||
|
DiffusionPoint::new(0, 2, 1.0 / 8.0),
|
||||||
];
|
];
|
||||||
|
|
||||||
impl Ditherer for FloydSteinbergDither {
|
static SIERRA_DITHER_POINTS: &[DiffusionPoint] = &[
|
||||||
fn dither(&mut self, img: &RgbImage, output: &mut EInkBuffer) {
|
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.
|
// create a copy of the image in Lab space, mutable.
|
||||||
let srgb = <&[Srgb<u8>]>::from_components(&**img);
|
let srgb = <&[Srgb<u8>]>::from_components(&**img);
|
||||||
let (xsize, ysize) = img.dimensions();
|
let (xsize, ysize) = img.dimensions();
|
||||||
|
@ -235,18 +273,14 @@ impl Ditherer for FloydSteinbergDither {
|
||||||
// set the color in the output buffer.
|
// set the color in the output buffer.
|
||||||
output.0[index] = nearest;
|
output.0[index] = nearest;
|
||||||
// take the error, and propagate it.
|
// 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 {
|
let Some(target_x) = x.checked_add_signed(point.xshift) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(target_y) = y.checked_add_signed(point.yshift) else {
|
let Some(target_y) = y.checked_add_signed(point.yshift) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let target = coord_to_idx(
|
let target = coord_to_idx(target_x, target_y, xsize);
|
||||||
target_x,
|
|
||||||
target_y,
|
|
||||||
xsize,
|
|
||||||
);
|
|
||||||
if let Some(pix) = temp_img.get(target) {
|
if let Some(pix) = temp_img.get(target) {
|
||||||
temp_img[target] = get_error_adjusted(pix, &err, point.scale);
|
temp_img[target] = get_error_adjusted(pix, &err, point.scale);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ pub mod display;
|
||||||
pub mod imageproc;
|
pub mod imageproc;
|
||||||
|
|
||||||
use crate::display::Wrapper;
|
use crate::display::Wrapper;
|
||||||
use crate::imageproc::{Ditherer, EInkBuffer, FloydSteinbergDither, NNDither};
|
use crate::imageproc::{DiffusionMatrix, EInkBuffer, ErrorDiffusionDither, Ditherer};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use image::RgbImage;
|
use image::RgbImage;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ fn main() -> anyhow::Result<()> {
|
||||||
let mut display = Wrapper::new()?;
|
let mut display = Wrapper::new()?;
|
||||||
|
|
||||||
let mut eink_buf = EInkBuffer::new(800, 480);
|
let mut eink_buf = EInkBuffer::new(800, 480);
|
||||||
let mut dither = FloydSteinbergDither{};
|
let dither = ErrorDiffusionDither::new(DiffusionMatrix::Atkinson);
|
||||||
|
|
||||||
dither.dither(&img, &mut eink_buf);
|
dither.dither(&img, &mut eink_buf);
|
||||||
let raw_buf = eink_buf.into_display_buffer();
|
let raw_buf = eink_buf.into_display_buffer();
|
||||||
|
@ -52,7 +52,7 @@ fn main() -> anyhow::Result<()> {
|
||||||
if matches!(cli.command, Command::Convert) {
|
if matches!(cli.command, Command::Convert) {
|
||||||
let img: RgbImage = image::io::Reader::open("myimage.png")?.decode()?.into();
|
let img: RgbImage = image::io::Reader::open("myimage.png")?.decode()?.into();
|
||||||
let mut eink_buf = EInkBuffer::new(800, 480);
|
let mut eink_buf = EInkBuffer::new(800, 480);
|
||||||
let mut dither = FloydSteinbergDither{};
|
let dither = ErrorDiffusionDither::new(DiffusionMatrix::Atkinson);
|
||||||
|
|
||||||
dither.dither(&img, &mut eink_buf);
|
dither.dither(&img, &mut eink_buf);
|
||||||
let raw_buf = eink_buf.into_display_buffer();
|
let raw_buf = eink_buf.into_display_buffer();
|
||||||
|
|
Loading…
Reference in a new issue