Add Floyd-Steinberg dithering

This commit is contained in:
Joscha 2023-07-30 21:26:29 +02:00
parent 452428ce5f
commit 6d522147f7
2 changed files with 84 additions and 9 deletions

View file

@ -5,7 +5,10 @@
//! compares two colors. Instead, a version of each algorithm should be compiled //! compares two colors. Instead, a version of each algorithm should be compiled
//! for each color space and difference combination. //! for each color space and difference combination.
use std::marker::PhantomData; use std::{
marker::PhantomData,
ops::{Add, Mul, Sub},
};
use image::RgbaImage; use image::RgbaImage;
use palette::{ use palette::{
@ -173,3 +176,65 @@ where
image image
} }
} }
pub struct AlgoFloydSteinberg;
impl AlgoFloydSteinberg {
fn update_pixel<C, D>(image: &mut RgbaImage, palette: &Palette<C>, x: u32, y: u32)
where
C: Copy,
C: IntoColor<Srgb>,
C: Sub<Output = C>,
D: Difference<C>,
Srgb: IntoColor<C>,
C: Add<Output = C>,
C: Mul<f32, Output = C>,
{
let pixel = image.get_pixel(x, y);
let before: C = util::pixel_to_color(*pixel);
let after = palette.nearest::<D>(before);
let error = after - before;
util::update_pixel_with_color(image.get_pixel_mut(x, y), after);
Self::diffuse_error(image, x + 1, y, error, 7.0 / 16.0);
if x > 0 {
Self::diffuse_error(image, x - 1, y + 1, error, 3.0 / 16.0);
}
Self::diffuse_error(image, x, y + 1, error, 5.0 / 16.0);
Self::diffuse_error(image, x + 1, y + 1, error, 1.0 / 16.0);
}
fn diffuse_error<C>(image: &mut RgbaImage, x: u32, y: u32, error: C, factor: f32)
where
C: Add<Output = C>,
C: IntoColor<Srgb>,
C: Mul<f32, Output = C>,
Srgb: IntoColor<C>,
{
if let Some(pixel) = image.get_pixel_mut_checked(x, y) {
let color: C = util::pixel_to_color(*pixel);
let color = color + error * factor;
util::update_pixel_with_color(pixel, color);
}
}
}
impl<C, D> Algorithm<C, D> for AlgoFloydSteinberg
where
C: Add<Output = C>,
C: Copy,
C: IntoColor<Srgb>,
C: Mul<f32, Output = C>,
C: Sub<Output = C>,
D: Difference<C>,
Srgb: IntoColor<C>,
{
fn run(mut image: RgbaImage, palette: Palette<C>) -> RgbaImage {
for y in 0..image.height() {
for x in 0..image.width() {
Self::update_pixel::<C, D>(&mut image, &palette, x, y);
}
}
image
}
}

View file

@ -14,6 +14,7 @@ use std::{
fmt, fmt,
io::{Cursor, Read, Write}, io::{Cursor, Read, Write},
num::ParseIntError, num::ParseIntError,
ops::{Add, Mul, Sub},
path::PathBuf, path::PathBuf,
str::FromStr, str::FromStr,
}; };
@ -23,8 +24,8 @@ use image::{ImageFormat, RgbaImage};
use mark::{ use mark::{
bw, bw,
dither::{ dither::{
AlgoRandom, AlgoThreshold, Algorithm, DiffCiede2000, DiffClamp, DiffEuclid, DiffHyAb, AlgoFloydSteinberg, AlgoRandom, AlgoThreshold, Algorithm, DiffCiede2000, DiffClamp,
DiffManhattan, DiffManhattanSquare, Difference, Palette, DiffEuclid, DiffHyAb, DiffManhattan, DiffManhattanSquare, Difference, Palette,
}, },
}; };
use palette::{color_difference::EuclideanDistance, Clamp, IntoColor, Lab, LinSrgb, Oklab, Srgb}; use palette::{color_difference::EuclideanDistance, Clamp, IntoColor, Lab, LinSrgb, Oklab, Srgb};
@ -70,6 +71,7 @@ impl BwCmd {
enum DitherAlgorithm { enum DitherAlgorithm {
Threshold, Threshold,
Random, Random,
FloydSteinberg,
} }
#[derive(Debug, Clone, Copy, clap::ValueEnum)] #[derive(Debug, Clone, Copy, clap::ValueEnum)]
@ -158,7 +160,7 @@ impl DitherCmd {
fn run_c<C>(self, image: RgbaImage) -> RgbaImage fn run_c<C>(self, image: RgbaImage) -> RgbaImage
where where
Srgb: IntoColor<C>, C: Add<C, Output = C>,
C: AsMut<[f32; 3]>, C: AsMut<[f32; 3]>,
C: AsRef<[f32; 3]>, C: AsRef<[f32; 3]>,
C: Clamp, C: Clamp,
@ -166,6 +168,9 @@ impl DitherCmd {
C: EuclideanDistance<Scalar = f32>, C: EuclideanDistance<Scalar = f32>,
C: IntoColor<Lab>, C: IntoColor<Lab>,
C: IntoColor<Srgb>, C: IntoColor<Srgb>,
C: Mul<f32, Output = C>,
C: Sub<C, Output = C>,
Srgb: IntoColor<C>,
{ {
match self.difference { match self.difference {
DitherDifference::Euclid => self.run_cd::<C, DiffEuclid>(image), DitherDifference::Euclid => self.run_cd::<C, DiffEuclid>(image),
@ -185,23 +190,28 @@ impl DitherCmd {
fn run_cd<C, D>(self, image: RgbaImage) -> RgbaImage fn run_cd<C, D>(self, image: RgbaImage) -> RgbaImage
where where
Srgb: IntoColor<C>, C: Add<C, Output = C>,
C: AsMut<[f32; 3]>, C: AsMut<[f32; 3]>,
C: Clamp, C: Clamp,
C: Copy, C: Copy,
C: IntoColor<Srgb>, C: IntoColor<Srgb>,
C: Mul<f32, Output = C>,
C: Sub<C, Output = C>,
D: Difference<C>, D: Difference<C>,
Srgb: IntoColor<C>,
{ {
use DitherAlgorithm::*;
match self.algorithm { match self.algorithm {
DitherAlgorithm::Threshold => self.run_acd::<AlgoThreshold, C, D>(image), Threshold => self.run_acd::<AlgoThreshold, C, D>(image),
DitherAlgorithm::Random => self.run_acd::<AlgoRandom, C, D>(image), Random => self.run_acd::<AlgoRandom, C, D>(image),
FloydSteinberg => self.run_acd::<AlgoFloydSteinberg, C, D>(image),
} }
} }
fn run_acd<A, C, D>(self, image: RgbaImage) -> RgbaImage fn run_acd<A, C, D>(self, image: RgbaImage) -> RgbaImage
where where
Srgb: IntoColor<C>,
A: Algorithm<C, D>, A: Algorithm<C, D>,
Srgb: IntoColor<C>,
{ {
let colors = self let colors = self
.palette .palette