Add Floyd-Steinberg dithering
This commit is contained in:
parent
452428ce5f
commit
6d522147f7
2 changed files with 84 additions and 9 deletions
|
|
@ -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::{
|
||||||
|
|
@ -124,7 +127,7 @@ impl<C, D> Algorithm<C, D> for AlgoThreshold
|
||||||
where
|
where
|
||||||
Srgb: IntoColor<C>,
|
Srgb: IntoColor<C>,
|
||||||
C: Copy,
|
C: Copy,
|
||||||
C: IntoColor<Srgb> ,
|
C: IntoColor<Srgb>,
|
||||||
D: Difference<C>,
|
D: Difference<C>,
|
||||||
{
|
{
|
||||||
fn run(mut image: RgbaImage, palette: Palette<C>) -> RgbaImage {
|
fn run(mut image: RgbaImage, palette: Palette<C>) -> RgbaImage {
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
24
src/main.rs
24
src/main.rs
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue