diff --git a/src/dither.rs b/src/dither.rs index 10e16c2..37fcfcf 100644 --- a/src/dither.rs +++ b/src/dither.rs @@ -5,7 +5,10 @@ //! compares two colors. Instead, a version of each algorithm should be compiled //! for each color space and difference combination. -use std::marker::PhantomData; +use std::{ + marker::PhantomData, + ops::{Add, Mul, Sub}, +}; use image::RgbaImage; use palette::{ @@ -124,7 +127,7 @@ impl Algorithm for AlgoThreshold where Srgb: IntoColor, C: Copy, - C: IntoColor , + C: IntoColor, D: Difference, { fn run(mut image: RgbaImage, palette: Palette) -> RgbaImage { @@ -173,3 +176,65 @@ where image } } + +pub struct AlgoFloydSteinberg; + +impl AlgoFloydSteinberg { + fn update_pixel(image: &mut RgbaImage, palette: &Palette, x: u32, y: u32) + where + C: Copy, + C: IntoColor, + C: Sub, + D: Difference, + Srgb: IntoColor, + C: Add, + C: Mul, + { + let pixel = image.get_pixel(x, y); + let before: C = util::pixel_to_color(*pixel); + let after = palette.nearest::(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(image: &mut RgbaImage, x: u32, y: u32, error: C, factor: f32) + where + C: Add, + C: IntoColor, + C: Mul, + Srgb: IntoColor, + { + 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 Algorithm for AlgoFloydSteinberg +where + C: Add, + C: Copy, + C: IntoColor, + C: Mul, + C: Sub, + D: Difference, + Srgb: IntoColor, +{ + fn run(mut image: RgbaImage, palette: Palette) -> RgbaImage { + for y in 0..image.height() { + for x in 0..image.width() { + Self::update_pixel::(&mut image, &palette, x, y); + } + } + image + } +} diff --git a/src/main.rs b/src/main.rs index 0eb80a5..e9b618b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use std::{ fmt, io::{Cursor, Read, Write}, num::ParseIntError, + ops::{Add, Mul, Sub}, path::PathBuf, str::FromStr, }; @@ -23,8 +24,8 @@ use image::{ImageFormat, RgbaImage}; use mark::{ bw, dither::{ - AlgoRandom, AlgoThreshold, Algorithm, DiffCiede2000, DiffClamp, DiffEuclid, DiffHyAb, - DiffManhattan, DiffManhattanSquare, Difference, Palette, + AlgoFloydSteinberg, AlgoRandom, AlgoThreshold, Algorithm, DiffCiede2000, DiffClamp, + DiffEuclid, DiffHyAb, DiffManhattan, DiffManhattanSquare, Difference, Palette, }, }; use palette::{color_difference::EuclideanDistance, Clamp, IntoColor, Lab, LinSrgb, Oklab, Srgb}; @@ -70,6 +71,7 @@ impl BwCmd { enum DitherAlgorithm { Threshold, Random, + FloydSteinberg, } #[derive(Debug, Clone, Copy, clap::ValueEnum)] @@ -158,7 +160,7 @@ impl DitherCmd { fn run_c(self, image: RgbaImage) -> RgbaImage where - Srgb: IntoColor, + C: Add, C: AsMut<[f32; 3]>, C: AsRef<[f32; 3]>, C: Clamp, @@ -166,6 +168,9 @@ impl DitherCmd { C: EuclideanDistance, C: IntoColor, C: IntoColor, + C: Mul, + C: Sub, + Srgb: IntoColor, { match self.difference { DitherDifference::Euclid => self.run_cd::(image), @@ -185,23 +190,28 @@ impl DitherCmd { fn run_cd(self, image: RgbaImage) -> RgbaImage where - Srgb: IntoColor, + C: Add, C: AsMut<[f32; 3]>, C: Clamp, C: Copy, C: IntoColor, + C: Mul, + C: Sub, D: Difference, + Srgb: IntoColor, { + use DitherAlgorithm::*; match self.algorithm { - DitherAlgorithm::Threshold => self.run_acd::(image), - DitherAlgorithm::Random => self.run_acd::(image), + Threshold => self.run_acd::(image), + Random => self.run_acd::(image), + FloydSteinberg => self.run_acd::(image), } } fn run_acd(self, image: RgbaImage) -> RgbaImage where - Srgb: IntoColor, A: Algorithm, + Srgb: IntoColor, { let colors = self .palette