From d9630d3342ebdbf4eb952a6090e68021de2acd3d Mon Sep 17 00:00:00 2001 From: ache Date: Wed, 23 Apr 2025 17:12:17 +0200 Subject: Worst atomic commit ever --- src/kernel.rs | 925 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 750 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 175 ++++++----- src/test_all.rs | 147 +++++++++ 4 files changed, 1928 insertions(+), 69 deletions(-) create mode 100644 src/kernel.rs create mode 100644 src/lib.rs create mode 100644 src/test_all.rs (limited to 'src') diff --git a/src/kernel.rs b/src/kernel.rs new file mode 100644 index 0000000..f1cb78d --- /dev/null +++ b/src/kernel.rs @@ -0,0 +1,925 @@ +#[derive(Debug, Clone)] +pub struct WeightedNeighbor { + pub dx: i32, + pub dy: i32, + pub weight: f32, +} + +#[derive(Debug, Clone)] +pub struct WeightedNeighborInteger { + pub dx: i32, + pub dy: i32, + pub weight: i32, +} + +pub struct KernelShift { + pub kernel: Vec, + pub bit_shift: u32, +} + +pub type Kernel = Vec; + +/** +* Atkinson kernel. +* X Represents the current pixel. +* +* [ 0 ] [ X ] [ 1 ] +* [ 1 ] [ 1 ] [ 1 ] +* [ 0 ] [ 1 ] [ 0 ] +* +* Weight multiplicator: 1.0 / 8.0 +*/ +pub const ATKINSON_KERNEL: [WeightedNeighbor; 6] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 1.0 / 8.0, + }, + WeightedNeighbor { + dx: 2, + dy: 0, + weight: 1.0 / 8.0, + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 1.0 / 8.0, + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 1.0 / 8.0, + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 1.0 / 8.0, + }, + WeightedNeighbor { + dx: 0, + dy: 2, + weight: 1.0 / 8.0, + }, +]; + +pub const ATKINSON_KERNEL_INTEGER: [WeightedNeighborInteger; 6] = [ + WeightedNeighborInteger { + dx: 1, + dy: 0, + weight: 1, + }, + WeightedNeighborInteger { + dx: 2, + dy: 0, + weight: 1, + }, + WeightedNeighborInteger { + dx: -1, + dy: 1, + weight: 1, + }, + WeightedNeighborInteger { + dx: 0, + dy: 1, + weight: 1, + }, + WeightedNeighborInteger { + dx: 1, + dy: 1, + weight: 1, + }, + WeightedNeighborInteger { + dx: 0, + dy: 2, + weight: 1, + }, +]; +pub const ATKINSON_KERNEL_BIT_SHIFT: u32 = 3; + +/** +* Floyd Steinberg kernel. + X Represent the current pixel. + + [ 0 ] [ X ] [ 7 ] + [ 3 ] [ 5 ] [ 1 ] + +* Weight multiplicator: 1.0 / 16.0 +*/ +pub const FLOYD_STEINBERG_KERNEL: [WeightedNeighbor; 4] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 7.0 / 16.0, + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 3.0 / 16.0, + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 5.0 / 16.0, + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 1.0 / 16.0, + }, +]; + +pub const FLOYD_STEINBERG_KERNEL_INTEGER: [WeightedNeighborInteger; 4] = [ + WeightedNeighborInteger { + dx: 1, + dy: 0, + weight: 7, + }, + WeightedNeighborInteger { + dx: -1, + dy: 1, + weight: 3, + }, + WeightedNeighborInteger { + dx: 0, + dy: 1, + weight: 5, + }, + WeightedNeighborInteger { + dx: 1, + dy: 1, + weight: 1, + }, +]; +pub const FLOYD_STEINBERG_KERNEL_BIT_SHIFT: u32 = 4; + +/** + * Fedoseev kernel. + * X Represent the current pixel. + * + * [ ] [ 0 ] [ X ] [ 0.5423 ] [ 0.0533 ] + * [ 0.0246 ] [ 0.2191 ] [ 0.4715 ] [-0.0023 ] [-0.1241 ] + * [-0.0065 ] [-0.0692 ] [ 0.0168 ] [-0.0952 ] [-0.0304 ] + */ +pub const FEDOSEEV_KERNEL: [WeightedNeighbor; 12] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 0.5423, + }, + WeightedNeighbor { + dx: 2, + dy: 0, + weight: 0.0533, + }, + WeightedNeighbor { + dx: -2, + dy: 1, + weight: -0.0246, + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 0.2191, + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 0.4715, + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: -0.0023, + }, + WeightedNeighbor { + dx: 2, + dy: 1, + weight: -0.1241, + }, + WeightedNeighbor { + dx: -2, + dy: 2, + weight: -0.0065, + }, + WeightedNeighbor { + dx: -1, + dy: 2, + weight: -0.0692, + }, + WeightedNeighbor { + dx: 0, + dy: 2, + weight: -0.0168, + }, + WeightedNeighbor { + dx: 1, + dy: 2, + weight: -0.0952, + }, + WeightedNeighbor { + dx: 2, + dy: 2, + weight: -0.0304, + }, +]; + +/** + * Fedoseev2 kernel. + * X Represent the current pixel. + + [ X ] [ 0.4364 ] + [ 0.5636 ] [ ] +*/ +pub const FEDOSEEV2_KERNEL: [WeightedNeighbor; 2] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 0.4364, + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 0.5636, + }, +]; + +/** + * Fedoseev3 kernel. + * X Represent the current pixel. + * + * [ 0 ] [ X ] [ 0.5221 ] + * [ 0.1854 ] [ 0.4689 ] [-0.1763 ] + */ +pub const FEDOSEEV3_KERNEL: [WeightedNeighbor; 4] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 0.5221, + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 0.1854, + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 0.4689, + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: -0.1763, + }, +]; + +/** + * Fedoseev kernel 4. +* X Represent the current pixel. +* +* [ 0 ] [ X ] [ 0.5221 ] +* [ 0.1854 ] [ 0.4689 ] [-0.1763 ] +*/ +pub const FEDOSEEV4_KERNEL: [WeightedNeighbor; 4] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 0.5221, + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 0.1854, + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 0.4689, + }, + WeightedNeighbor { + dx: -1, + dy: 0, + weight: -0.1763, + }, +]; + +/** +* Wong-Allebach kernel. + X Represent the current pixel. + + [ 0 ] [ X ] [ 0.2911 ] + [ 0.1373 ] [ 0.3457 ] [ 0.2258 ] +*/ +pub const WONG_ALLEBACH_KERNEL: [WeightedNeighbor; 4] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 0.2911, + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 0.1373, + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 0.3457, + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 0.2258, + }, +]; + +/** +* Jarvis-Judice-Ninke' kernel. + X Represent the current pixel. + + [ 0 ] [ 0 ] [ X ] [ 7 ] [ 5 ] + [ 3 ] [ 5 ] [ 7 ] [ 5 ] [ 3 ] + [ 1 ] [ 3 ] [ 5 ] [ 3 ] [ 1 ] + +* Weight multiplicator: 1.0 / 48.0 +*/ +pub const JARVIS_JUDICE_NINKE_KERNEL: [WeightedNeighbor; 12] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 7. / 48., + }, + WeightedNeighbor { + dx: 2, + dy: 0, + weight: 5. / 48., + }, + WeightedNeighbor { + dx: -2, + dy: 1, + weight: 3. / 48., + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 5. / 48., + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 7. / 48., + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 5. / 48., + }, + WeightedNeighbor { + dx: 2, + dy: 1, + weight: 3. / 48., + }, + WeightedNeighbor { + dx: -2, + dy: 2, + weight: 1. / 48., + }, + WeightedNeighbor { + dx: -1, + dy: 2, + weight: 3. / 48., + }, + WeightedNeighbor { + dx: 0, + dy: 2, + weight: 5. / 48., + }, + WeightedNeighbor { + dx: 1, + dy: 2, + weight: 3. / 48., + }, + WeightedNeighbor { + dx: 2, + dy: 2, + weight: 1. / 48., + }, +]; + +/** + * Stucki kernel. + * X Represents the current pixel. + * + * [ 0 ] [ 0 ] [ X ] [ 8 ] [ 4 ] + * [ 2 ] [ 4 ] [ 8 ] [ 4 ] [ 2 ] + * [ 1 ] [ 2 ] [ 4 ] [ 2 ] [ 1 ] + * + * Weight multiplicator: 1.0 / 42.0 + */ +pub const STUCKI_KERNEL: [WeightedNeighbor; 12] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 8. / 42., + }, + WeightedNeighbor { + dx: 2, + dy: 0, + weight: 4. / 42., + }, + WeightedNeighbor { + dx: -2, + dy: 1, + weight: 2. / 42., + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 4. / 42., + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 8. / 42., + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 4. / 42., + }, + WeightedNeighbor { + dx: 2, + dy: 1, + weight: 2. / 42., + }, + WeightedNeighbor { + dx: -2, + dy: 2, + weight: 1. / 42., + }, + WeightedNeighbor { + dx: -1, + dy: 2, + weight: 2. / 42., + }, + WeightedNeighbor { + dx: 0, + dy: 2, + weight: 4. / 42., + }, + WeightedNeighbor { + dx: 1, + dy: 2, + weight: 2. / 42., + }, + WeightedNeighbor { + dx: 2, + dy: 2, + weight: 1. / 42., + }, +]; +/** + * The Burkes kernel. +* X Represents the current pixel. +* +* [ ] [ ] [ X ] [ 4 ] [ 2 ] +* [ 1 ] [ 2 ] [ 4 ] [ 2 ] [ 1 ] +* +* Weight multiplicator: 1.0 / 16.0 +*/ +pub const BURKES_KERNEL: [WeightedNeighbor; 7] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 4. / 16., + }, + WeightedNeighbor { + dx: 2, + dy: 0, + weight: 2. / 16., + }, + WeightedNeighbor { + dx: -2, + dy: 1, + weight: 1. / 16., + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 2. / 16., + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 4. / 16., + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 2. / 16., + }, + WeightedNeighbor { + dx: 2, + dy: 1, + weight: 1. / 16., + }, +]; + +pub const BURKES_KERNEL_INTEGER: [WeightedNeighborInteger; 7] = [ + WeightedNeighborInteger { + dx: 1, + dy: 0, + weight: 4, + }, + WeightedNeighborInteger { + dx: 2, + dy: 0, + weight: 2, + }, + WeightedNeighborInteger { + dx: -2, + dy: 1, + weight: 1, + }, + WeightedNeighborInteger { + dx: -1, + dy: 1, + weight: 2, + }, + WeightedNeighborInteger { + dx: 0, + dy: 1, + weight: 4, + }, + WeightedNeighborInteger { + dx: 1, + dy: 1, + weight: 2, + }, + WeightedNeighborInteger { + dx: 2, + dy: 1, + weight: 1, + }, +]; +pub const BURKES_KERNEL_BIT_SHIFT: u32 = 4; + +pub const KONG_KUI_KERNEL_INTEGER: [WeightedNeighborInteger; 2] = [ + WeightedNeighborInteger { + dx: 1, + dy: 0, + weight: 1, + }, + WeightedNeighborInteger { + dx: 0, + dy: 1, + weight: 1, + }, +]; +pub const KONG_KUI_KERNEL_BIT_SHIFT: u32 = 1; + +/** +* Sierra 3 kernel. +* X Represents the current pixel. +* +* [ ] [ ] [ X ] [ 5 ] [ 3 ] +* [ 2 ] [ 4 ] [ 5 ] [ 4 ] [ 2 ] +* [ ] [ 2 ] [ 3 ] [ 2 ] [ ] +* +* Weight multiplicator: 1.0 / 32.0 +*/ +pub const SIERRA3_KERNEL: [WeightedNeighbor; 10] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 5. / 32., + }, + WeightedNeighbor { + dx: 2, + dy: 0, + weight: 3. / 32., + }, + WeightedNeighbor { + dx: -2, + dy: 1, + weight: 2. / 32., + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 4. / 32., + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 5. / 32., + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 4. / 32., + }, + WeightedNeighbor { + dx: 2, + dy: 1, + weight: 2. / 32., + }, + WeightedNeighbor { + dx: -1, + dy: 2, + weight: 2. / 32., + }, + WeightedNeighbor { + dx: 0, + dy: 2, + weight: 3. / 32., + }, + WeightedNeighbor { + dx: 1, + dy: 2, + weight: 2. / 32., + }, +]; + +pub const SIERRA3_KERNEL_INTEGER: [WeightedNeighborInteger; 10] = [ + WeightedNeighborInteger { + dx: 1, + dy: 0, + weight: 5, + }, + WeightedNeighborInteger { + dx: 2, + dy: 0, + weight: 3, + }, + WeightedNeighborInteger { + dx: -2, + dy: 1, + weight: 2, + }, + WeightedNeighborInteger { + dx: -1, + dy: 1, + weight: 4, + }, + WeightedNeighborInteger { + dx: 0, + dy: 1, + weight: 5, + }, + WeightedNeighborInteger { + dx: 1, + dy: 1, + weight: 4, + }, + WeightedNeighborInteger { + dx: 2, + dy: 1, + weight: 2, + }, + WeightedNeighborInteger { + dx: -1, + dy: 2, + weight: 2, + }, + WeightedNeighborInteger { + dx: 0, + dy: 2, + weight: 3, + }, + WeightedNeighborInteger { + dx: 1, + dy: 2, + weight: 2, + }, +]; +pub const SIERRA3_KERNEL_BIT_SHIFT: u32 = 5; + +/** Sierra two-row kernel. Also known as Sierra2. +* X Represents the current pixel. +* +* [ ] [ ] [ X ] [ 4 ] [ 3 ] +* [ 1 ] [ 2 ] [ 3 ] [ 2 ] [ 1 ] +* +* Weight multiplicator: 1.0 / 16.0 +*/ +pub const SIERRA2_KERNEL: [WeightedNeighbor; 7] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 4. / 16., + }, + WeightedNeighbor { + dx: 2, + dy: 0, + weight: 3. / 16., + }, + WeightedNeighbor { + dx: -2, + dy: 1, + weight: 1. / 16., + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 2. / 16., + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 3. / 16., + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 2. / 16., + }, + WeightedNeighbor { + dx: 2, + dy: 1, + weight: 1. / 16., + }, +]; + +pub const SIERRA2_KERNEL_INTEGER: [WeightedNeighborInteger; 7] = [ + WeightedNeighborInteger { + dx: 1, + dy: 0, + weight: 4, + }, + WeightedNeighborInteger { + dx: 2, + dy: 0, + weight: 3, + }, + WeightedNeighborInteger { + dx: -2, + dy: 1, + weight: 1, + }, + WeightedNeighborInteger { + dx: -1, + dy: 1, + weight: 2, + }, + WeightedNeighborInteger { + dx: 0, + dy: 1, + weight: 3, + }, + WeightedNeighborInteger { + dx: 1, + dy: 1, + weight: 2, + }, + WeightedNeighborInteger { + dx: 2, + dy: 1, + weight: 1, + }, +]; +pub const SIERRA2_KERNEL_BIT_SHIFT: u32 = 4; + +/** Sierra Lite kernel +* X Represents the current pixel. Also known as Sierra2-4A. +* +* [ ] [ X ] [ 2 ] +* [ 1 ] [ 1 ] [ ] +* +* Weight multiplicator: 1.0 / 4.0 +*/ +pub const SIERRA_LITE_KERNEL: [WeightedNeighbor; 3] = [ + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 2. / 4., + }, + WeightedNeighbor { + dx: -1, + dy: 1, + weight: 1. / 4., + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 1. / 4., + }, +]; + +pub const SIERRA_LITE_KERNEL_INTEGER: [WeightedNeighborInteger; 3] = [ + WeightedNeighborInteger { + dx: 1, + dy: 0, + weight: 2, + }, + WeightedNeighborInteger { + dx: -1, + dy: 1, + weight: 1, + }, + WeightedNeighborInteger { + dx: 0, + dy: 1, + weight: 1, + }, +]; +pub const SIERRA_LITE_KERNEL_BIT_SHIFT: u32 = 2; + +/** +* Prewitt kernel. +* X Represent the current pixel. +* NOTE: Completly hallucinated. +* +* [ 0 0 0 ] [ 0.125 ] [ 0.125 ] +* [ 0 0 0 ] [ 0.00 ] [ 0.00 ] +* [ 0 1 0 ] [ 0.125 ] +* +*/ +pub const PREWITT_KERNEL: [WeightedNeighbor; 4] = [ + WeightedNeighbor { + // BUG: Useless + dx: 0, + dy: 0, + weight: 0.125, + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 0.125, + }, + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 0.125, + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 0.125, + }, +]; + +/** +* Roberts kernel. +* X Represent the current pixel. +* Completly hallucinated. +* +* [ 0 0 0 ] [ 0.25 ] [ 0.25 ] +* [ 0 0 0 ] [ 0.00 ] [ 0.00 ] +* [ 0 1 0 ] [ 0.2258 ] [ 0.2258 ] +*/ +pub const ROBERTS_KERNEL: [WeightedNeighbor; 4] = [ + WeightedNeighbor { + // BUG: Useless + dx: 0, + dy: 0, + weight: 0.25, + }, + WeightedNeighbor { + dx: 0, + dy: 1, + weight: 0.25, + }, + WeightedNeighbor { + dx: 1, + dy: 0, + weight: 0.25, + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 0.25, + }, +]; + +/** +* Sobel kernel. + X Represent the current pixel. + Completly allucinated. + + [ -1 -2 -1 ] [ X ] [ 0.25 ] + [ 0 0 0 ] [ 0.50 ] [ 0.00 ] + [ 1 2 1 ] [ 0.25 ] [ 0.25 ] + */ +pub const SOBEL_KERNEL: [WeightedNeighbor; 4] = [ + WeightedNeighbor { + dx: -1, + dy: -1, + weight: -0.25, + }, + WeightedNeighbor { + dx: 0, + dy: -1, + weight: -0.5, + }, + WeightedNeighbor { + dx: 1, + dy: -1, + weight: -0.25, + }, + WeightedNeighbor { + dx: 1, + dy: 1, + weight: 0.25, + }, +]; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a479934 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,750 @@ +pub mod kernel; + +use image::{ImageBuffer, Rgb, RgbImage}; +use palette::{LinSrgb, Srgb, color_difference::EuclideanDistance}; +use rand::rngs::SmallRng; +use rand::{RngCore, SeedableRng}; + +const BLACK_SRGB: Srgb = Srgb::new(0., 0., 0.); +const WHITE_SRGB: Srgb = Srgb::new(1., 1., 1.); + +const BLACK_LINEAR_SRGB: LinSrgb = LinSrgb::new(0., 0., 0.); +const WHITE_LINEAR_SRGB: LinSrgb = LinSrgb::new(1., 1., 1.); + +#[allow(dead_code)] +fn dither_chose_color_linear_sqrt(diffused_error: f32, pixel: u8) -> (u8, f32) { + let grey_pixel = Srgb::new(pixel, pixel, pixel).into_linear(); + let distance_to_black = BLACK_LINEAR_SRGB.distance(grey_pixel).sqrt() + diffused_error; + let distance_to_white = WHITE_LINEAR_SRGB.distance(grey_pixel).sqrt() + diffused_error; + + if distance_to_black.abs() < distance_to_white.abs() { + (0, -distance_to_black) + } else { + (255, distance_to_white) + } +} + +#[allow(dead_code)] +fn dither_chose_color_linear(diffused_error: f32, pixel: u8) -> (u8, f32) { + let grey_pixel = Srgb::new(pixel, pixel, pixel).into_linear(); + let distance_to_black = BLACK_LINEAR_SRGB.distance(grey_pixel) + diffused_error; + let distance_to_white = WHITE_LINEAR_SRGB.distance(grey_pixel) + diffused_error; + + if distance_to_black.abs() < distance_to_white.abs() { + (0, -distance_to_black) + } else { + (255, distance_to_white) + } +} + +#[allow(dead_code)] +fn dither_chose_color(diffused_error: f32, pixel: u8) -> (u8, f32) { + let grey_pixel: Srgb = Srgb::new(pixel, pixel, pixel).into_format(); + let distance_to_black = BLACK_SRGB.distance(grey_pixel).sqrt().sqrt() + diffused_error; + let distance_to_white = WHITE_SRGB.distance(grey_pixel).sqrt().sqrt() + diffused_error; + + if distance_to_black.abs() < distance_to_white.abs() { + (0, distance_to_black.abs()) + } else { + (255, distance_to_white.abs()) + } +} + +#[allow(dead_code)] +fn dither_atkinson_chose_color(diffused_error: f32, pixel: f32) -> (u8, f32) { + let distance_to_black = -pixel + diffused_error; + let distance_to_white = (255. - pixel) + diffused_error; + + if distance_to_black.abs() < distance_to_white.abs() { + (0, distance_to_black) + } else { + (255, distance_to_white) + } +} + +#[allow(dead_code)] +fn dither_integer_chose_color(diffused_error: i32, pixel: i32) -> (u8, i32) { + let distance_to_black = -pixel + diffused_error; + let distance_to_white = 255 - pixel + diffused_error; + + if distance_to_black.abs() < distance_to_white.abs() { + (0, distance_to_black) + } else { + (255, distance_to_white) + } +} +fn dither_integer_chose_8colors( + diffused_error_r: i32, + diffused_error_g: i32, + diffused_error_b: i32, + pixel: Rgb, +) -> (Rgb, [i32; 3]) { + let Rgb([r, g, b]) = pixel; + let (red, green, blue): (i32, i32, i32) = (r as i32, g as i32, b as i32); + let dist_to_black_r = -red + diffused_error_r; + let dist_to_white_r = 255 - red + diffused_error_r; + + let dist_to_black_g = -green + diffused_error_g; + let dist_to_white_g = 255 - green + diffused_error_g; + + let dist_to_black_b = -blue + diffused_error_b; + let dist_to_white_b = 255 - blue + diffused_error_b; + + let mut ret_diffused_error: [i32; 3] = [0, 0, 0]; + let mut ret_pix = Rgb([255, 0, 0]); + ret_pix.0[0] = if dist_to_black_r.abs() < dist_to_white_r.abs() { + ret_diffused_error[0] = dist_to_black_r; + 0 + } else { + ret_diffused_error[0] = dist_to_white_r; + 255 + }; + ret_pix.0[1] = if dist_to_black_g.abs() < dist_to_white_g.abs() { + ret_diffused_error[1] = dist_to_black_g; + 0 + } else { + ret_diffused_error[1] = dist_to_white_g; + 255 + }; + ret_pix.0[2] = if dist_to_black_b.abs() < dist_to_white_b.abs() { + ret_diffused_error[2] = dist_to_black_b; + 0 + } else { + ret_diffused_error[2] = dist_to_white_b; + 255 + }; + + (ret_pix, ret_diffused_error) +} + +#[allow(dead_code)] +fn l_to_srgb(l: f32) -> f32 { + if l <= 0.0031308 { + l * 12.92 + } else { + f32::powf(l * 0.9478672986 + 0.0521327014, 0.41666) + } +} + +#[allow(dead_code)] +fn dither_float_chose_color(diffused_error: f32, pixel: f32) -> (u8, f32) { + let distance_to_black = -pixel; + let distance_to_white = 255. - pixel + diffused_error; + + if distance_to_black.abs() + diffused_error < distance_to_white.abs() { + (0, distance_to_black) + } else { + (255, distance_to_white) + } +} +#[inline] +fn threshold_1b(pix: u8) -> u8 { + if pix > 128 { 255 } else { 0 } +} + +#[inline] +fn threshold_3b(pix: &mut Rgb) { + pix.0[0] = threshold_1b(pix.0[0]); + pix.0[1] = threshold_1b(pix.0[1]); + pix.0[2] = threshold_1b(pix.0[2]); +} + +#[allow(dead_code)] +pub fn dither_threshold_1b(image: &mut RgbImage) { + for pixel_ in &mut image.pixels_mut() { + let Rgb([r, g, b]) = pixel_; + // The +1 is to round up to the "nearest" integer. + let pixel = if *r as u32 + *g as u32 + *b as u32 + 1 > 3 * 128 { + 255 + } else { + 0 + }; + *pixel_ = Rgb([pixel, pixel, pixel]); + } +} + +#[allow(dead_code)] +pub fn dither_threshold_3b(image: &mut RgbImage) { + for pixel_ in &mut image.pixels_mut() { + threshold_3b(pixel_); + } +} + +#[allow(dead_code)] +fn dither_float_chose_color_palette(diffused_error: f32, pixel: f32) -> (u8, f32) { + let distance_to_black = -pixel; + let distance_to_white = 255. - pixel + diffused_error; + + if distance_to_black.abs() + diffused_error < distance_to_white.abs() { + (0, distance_to_black) + } else { + (255, distance_to_white) + } +} +pub fn dither_random(image: &mut RgbImage) { + let mut rng = SmallRng::seed_from_u64(1); + let mut rand_number = rng.next_u64(); + + for pixel_ in &mut image.pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let pixel = (*r as u32 + *g as u32 + *b as u32 + 1) as u32 / 3; // The +1 is to round up to the "nearest" integer. + *pixel_ = if pixel < (rand_number & 0xFF) as u32 { + image::Rgb([0, 0, 0]) + } else { + image::Rgb([255, 255, 255]) + }; + + // Quick and dirty optimisation to not call next_u64 on every single pixel. + rand_number >>= 8; + if rand_number == 0 { + rand_number = rng.next_u64(); + } + } +} +pub fn dither_random_color(image: &mut RgbImage) { + let mut rng = SmallRng::seed_from_u64(1); + + for pixel_ in &mut image.pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let rand_number = rng.next_u64(); + *r = if *r < (rand_number & 0xFF) as u8 { + 0 + } else { + 255 + }; + *g = if *g < (rand_number & 0xFF00 >> 8) as u8 { + 0 + } else { + 255 + }; + *b = if *b < ((rand_number & 0xFF0000 >> 16) as u8) { + 0 + } else { + 255 + }; + } +} + +pub fn dither_kong_kui(image: &mut RgbImage) { + let (width, _height) = image.dimensions(); + let error_buffer_size = width as usize; + let mut error: Vec = vec![0; error_buffer_size]; + + for (x, _y, pixel_) in &mut image.enumerate_pixels_mut() { + let Rgb([r, g, b]) = pixel_; + + let err_pos = x as usize; + let sum = (*r as i32 + *g as i32 + *b as i32 + 1) / 3; + let pix: i32 = sum + error[err_pos]; + + let (err, pixel) = if pix > 127 { + ((pix - 255) >> 1, 255) + } else { + (pix >> 1, 0) + }; + assert!(pixel == 0 || pixel == 255); + + error[err_pos] = err; + error[(err_pos + 1) % error_buffer_size] += err; + + *pixel_ = Rgb([pixel, pixel, pixel]); + } +} + +pub fn dither_shift(image: &mut RgbImage, kernel: kernel::KernelShift) { + let (width, _height) = image.dimensions(); + let width = width as usize; + let error_buffer_size = kernel + .kernel + .iter() + .map(|k| k.dx + k.dy * width as i32) + .max() + .unwrap() as usize; + assert!(error_buffer_size > 0); + let mut error: Vec = vec![0; error_buffer_size]; + + for (x, y, pixel_) in &mut image.enumerate_pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let pixel = (*r as i32 + *g as i32 + *b as i32 + 1) as i32 / 3; // The +1 is to round up to the "nearest" integer. + let err_pos: u32 = (y * width as u32 + x) % (error_buffer_size as u32); + + let (pixel, err) = dither_integer_chose_color(error[err_pos as usize], pixel); + + error[err_pos as usize] = 0; + for e in &kernel.kernel { + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize] += (err * e.weight) >> kernel.bit_shift; + } + + *pixel_ = image::Rgb([pixel, pixel, pixel]); + } +} +pub fn dither_shift_8colors(image: &mut RgbImage, kernel: kernel::KernelShift) { + let (width, _height) = image.dimensions(); + let width = width as usize; + let error_buffer_size = kernel + .kernel + .iter() + .map(|k| k.dx + k.dy * width as i32) + .max() + .unwrap() as usize; + assert!(error_buffer_size > 0); + let (mut error_r, mut error_g, mut error_b): (Vec, Vec, Vec) = ( + vec![0; error_buffer_size], + vec![0; error_buffer_size], + vec![0; error_buffer_size], + ); + + for (x, y, pixel) in &mut image.enumerate_pixels_mut() { + let err_pos: u32 = (y * width as u32 + x) % (error_buffer_size as u32); + + let (p, err) = dither_integer_chose_8colors( + error_r[err_pos as usize], + error_g[err_pos as usize], + error_b[err_pos as usize], + *pixel, + ); + + for e in &kernel.kernel { + let e_pos = (err_pos as i32 + e.dx + width as i32 * e.dy) + .rem_euclid(error_buffer_size as i32) as usize; + error_r[e_pos] += (err[0] * e.weight) >> kernel.bit_shift; + error_g[e_pos] += (err[1] * e.weight) >> kernel.bit_shift; + error_b[e_pos] += (err[2] * e.weight) >> kernel.bit_shift; + } + error_r[err_pos as usize] = 0; + error_g[err_pos as usize] = 0; + error_b[err_pos as usize] = 0; + + *pixel = p; + } +} + +pub fn dither_ref(image: &mut RgbImage, kernel: &kernel::Kernel) { + let (width, _height) = image.dimensions(); + let width = width as usize; + let error_buffer_size = kernel + .iter() + .map(|k| k.dx + k.dy * width as i32) + .max() + .unwrap() as usize; + assert!(error_buffer_size > 0); + let mut error: Vec = vec![0.; error_buffer_size]; + + for (x, y, pixel_) in &mut image.enumerate_pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let pixel = (*r as u32 + *g as u32 + *b as u32 + 1) as i32 / 3; // The +1 is to round up to the "nearest" integer. + let err_pos: u32 = (y * width as u32 + x) % (error_buffer_size as u32); + + let (pixel, err) = dither_float_chose_color(error[err_pos as usize], pixel as f32); + + for e in kernel { + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize] += err as f32 * e.weight; + } + error[err_pos as usize] = 0.; + + *pixel_ = image::Rgb([pixel, pixel, pixel]); + } +} +pub fn dither_linear_3b(image: &mut RgbImage, kernel: kernel::Kernel) { + let (width, _height) = image.dimensions(); + let width = width as usize; + let error_buffer_size = kernel + .iter() + .map(|k| k.dx + k.dy * width as i32) + .max() + .unwrap() as usize; + assert!(error_buffer_size > 0); + let mut error: Vec = vec![0.; error_buffer_size * 3]; + + for (x, y, pixel_) in &mut image.enumerate_pixels_mut() { + for i in [0, 1, 2].iter() { + let c = pixel_.0[*i]; + let err_pos: u32 = (y * width as u32 + x) % (error_buffer_size as u32); + let err_biais = (*i as u32) * (error_buffer_size as u32); + + let linear: LinSrgb = + Srgb::new(c as f32 / 255., c as f32 / 255., c as f32 / 255.).into_linear(); + + let (pixel, err) = dither_atkinson_linear_chose_color_sqrt( + error[err_pos as usize + err_biais as usize], + &linear, + ); + + error[err_pos as usize + err_biais as usize] = 0.; + for e in &kernel { + error[(err_pos as i32 + e.dx + width as i32 * e.dy) + .rem_euclid(error_buffer_size as i32) as usize + + err_biais as usize] += err as f32 * e.weight; + } + pixel_.0[*i] = pixel; + } + } +} +pub fn dither_linear(image: &mut RgbImage, kernel: kernel::Kernel) { + let (width, _height) = image.dimensions(); + let width = width as usize; + let error_buffer_size = kernel + .iter() + .map(|k| k.dx + k.dy * width as i32) + .max() + .unwrap() as usize; + assert!(error_buffer_size > 0); + let mut error: Vec = vec![0.; error_buffer_size]; + + for (x, y, pixel_) in &mut image.enumerate_pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let pixel = (*r as u32 + *g as u32 + *b as u32 + 1) as i32 / 3; // The +1 is to round up to the "nearest" integer. + let err_pos: u32 = (y * width as u32 + x) % (error_buffer_size as u32); + + let linear: LinSrgb = Srgb::new( + pixel as f32 / 255., + pixel as f32 / 255., + pixel as f32 / 255., + ) + .into_linear(); + + let (pixel, err) = + dither_atkinson_linear_chose_color_sqrt(error[err_pos as usize], &linear); + + for e in &kernel { + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize] += err as f32 * e.weight; + } + error[err_pos as usize] = 0.; + + *pixel_ = image::Rgb([pixel, pixel, pixel]); + } +} +pub fn dither(image: &mut RgbImage, kernel: kernel::Kernel) { + let (width, _height) = image.dimensions(); + let width = width as usize; + let error_buffer_size = kernel + .iter() + .map(|k| k.dx + k.dy * width as i32) + .max() + .unwrap() as usize; + assert!(error_buffer_size > 0); + let mut error: Vec = vec![0.; error_buffer_size]; + + for (x, y, pixel_) in &mut image.enumerate_pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let pixel = (*r as u32 + *g as u32 + *b as u32 + 1) as i32 / 3; // The +1 is to round up to the "nearest" integer. + let err_pos: u32 = (y * width as u32 + x) % (error_buffer_size as u32); + + let (pixel, err) = dither_float_chose_color(error[err_pos as usize], pixel as f32); + + for e in &kernel { + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize] += err as f32 * e.weight; + } + error[err_pos as usize] = 0.; + + *pixel_ = image::Rgb([pixel, pixel, pixel]); + } +} +pub fn dither_float(image: &mut RgbImage, kernel: kernel::Kernel) { + let (width, _height) = image.dimensions(); + let width = width as usize; + let error_buffer_size = kernel + .iter() + .map(|k| k.dx + k.dy * width as i32) + .max() + .unwrap() as usize; + assert!(error_buffer_size > 0); + let mut error: Vec = vec![0.; error_buffer_size]; + + for (x, y, pixel_) in &mut image.enumerate_pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let pixel = (*r as f32 + *g as f32 + *b as f32) as f32 / 3.; + let err_pos: u32 = (y * width as u32 + x) % (error_buffer_size as u32); + + let (pixel, err) = dither_float_chose_color(error[err_pos as usize], pixel); + + for e in &kernel { + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize] += err * e.weight; + } + error[err_pos as usize] = 0.; + + *pixel_ = image::Rgb([pixel, pixel, pixel]); + } +} + +pub type DitherFn = for<'a> fn(&'a mut ImageBuffer, Vec>); + +#[allow(dead_code)] +pub fn dither_atkinson_chose_color_base(diffused_error: f32, pixel: f32) -> (u8, f32) { + if (pixel + diffused_error) > 127. { + (255, (pixel + diffused_error - 255.)) + } else { + (0, (pixel + diffused_error)) + } +} + +pub fn dither_atkinson(image: &mut RgbImage) { + let (width, _height) = image.dimensions(); + let mut error: Vec = vec![0.; (2 * width) as usize]; + + for (x, y, pixel_) in &mut image.enumerate_pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let pixel = (*r as u32 + *g as u32 + *b as u32) as f32 / 3.; + let err_pos: u32 = (y * width + x) % (2 * width); + + let (pixel, err) = dither_atkinson_chose_color(error[err_pos as usize], pixel); + + error[err_pos as usize] = 0.; + error[((err_pos + 1) % (2 * width)) as usize] += err / 8.; + error[((err_pos + 2) % (2 * width)) as usize] += err / 8.; + error[((err_pos + width + 1) % (2 * width)) as usize] += err / 8.; + error[((err_pos + width - 1) % (2 * width)) as usize] += err / 8.; + error[((err_pos + width) % (2 * width)) as usize] += err / 8.; + error[((err_pos + 2 * width) % (2 * width)) as usize] += err / 8.; + + *pixel_ = image::Rgb([pixel, pixel, pixel]); + } +} + +pub fn dither_atkinson_base(image: &mut RgbImage) { + let (width, _height) = image.dimensions(); + let mut error: Vec = vec![0; (2 * width) as usize]; + + for (x, y, pixel_) in &mut image.enumerate_pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let pixel = (*r as u32 + *g as u32 + *b as u32 + 1) as i32 / 3; + let err_pos: u32 = (y * width + x) % (2 * width); + + let (pixel, err) = if (pixel + error[err_pos as usize]) > 128 { + (255, (pixel + error[err_pos as usize] - 255) >> 3) + } else { + (0, (pixel + error[err_pos as usize]) >> 3) + }; + + error[err_pos as usize] = 0; + error[((err_pos + 1) % (2 * width)) as usize] += err; + error[((err_pos + 2) % (2 * width)) as usize] += err; + error[((err_pos + width + 1) % (2 * width)) as usize] += err; + error[((err_pos + width - 1) % (2 * width)) as usize] += err; + error[((err_pos + width) % (2 * width)) as usize] += err; + error[((err_pos + 2 * width) % (2 * width)) as usize] += err; + + *pixel_ = image::Rgb([pixel, pixel, pixel]); + } +} + +pub fn dither_atkinson_linear_chose_color(diffused_error: f32, pixel: &LinSrgb) -> (u8, f32) { + let black: LinSrgb = Srgb::new(0u8, 0u8, 0u8).into_linear(); + let white: LinSrgb = Srgb::new(255, 255, 255).into_linear(); + + let error_black = diffused_error - black.distance(*pixel); + let error_white = diffused_error + white.distance(*pixel); + + if error_black.abs() < error_white.abs() { + (0, error_black) + } else { + (255, error_white) + } +} + +pub fn dither_atkinson_linear_chose_color_sqrt( + diffused_error: f32, + pixel: &LinSrgb, +) -> (u8, f32) { + let black: LinSrgb = Srgb::new(0u8, 0u8, 0u8).into_linear(); + let white: LinSrgb = Srgb::new(255, 255, 255).into_linear(); + + let error_black = diffused_error - black.distance(*pixel).sqrt(); + let error_white = diffused_error + white.distance(*pixel).sqrt(); + + if error_black.abs() < error_white.abs() { + (0, error_black) + } else { + (255, error_white) + } +} + +pub fn dither_atkinson_linear(image: &mut RgbImage) { + let (width, _height) = image.dimensions(); + let mut error: Vec = vec![0.; (3 * width) as usize]; + + for (x, y, pixel_) in &mut image.enumerate_pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let linear: LinSrgb = Srgb::new(*r, *g, *b).into_linear(); + let err_pos: u32 = (y * width + x) % (3 * width); + + let (pixel, err) = dither_atkinson_linear_chose_color(error[err_pos as usize], &linear); + + error[err_pos as usize] = 0.; + error[((err_pos + 1) % (3 * width)) as usize] += err / 8.; + error[((err_pos + 2) % (3 * width)) as usize] += err / 8.; + error[((err_pos + width + 1) % (3 * width)) as usize] += err / 8.; + error[((err_pos + width - 1) % (3 * width)) as usize] += err / 8.; + error[((err_pos + width) % (3 * width)) as usize] += err / 8.; + error[((err_pos + 2 * width) % (3 * width)) as usize] += err / 8.; + + *pixel_ = image::Rgb([pixel, pixel, pixel]); + } +} + +pub fn dither_atkinson_linear_sqrt(image: &mut RgbImage) { + let (width, _height) = image.dimensions(); + let mut error: Vec = vec![0.; (3 * width) as usize]; + + for (x, y, pixel_) in &mut image.enumerate_pixels_mut() { + let Rgb([r, g, b]) = pixel_; + let linear: LinSrgb = Srgb::new(*r, *g, *b).into_linear(); + let err_pos: u32 = (y * width + x) % (3 * width); + + let (pixel, err) = + dither_atkinson_linear_chose_color_sqrt(error[err_pos as usize], &linear); + + error[err_pos as usize] = 0.; + error[((err_pos + 1) % (3 * width)) as usize] += err / 8.; + error[((err_pos + 2) % (3 * width)) as usize] += err / 8.; + error[((err_pos + width + 1) % (3 * width)) as usize] += err / 8.; + error[((err_pos + width - 1) % (3 * width)) as usize] += err / 8.; + error[((err_pos + width) % (3 * width)) as usize] += err / 8.; + error[((err_pos + 2 * width) % (3 * width)) as usize] += err / 8.; + + *pixel_ = image::Rgb([pixel, pixel, pixel]); + } +} + +#[allow(dead_code)] +fn chose_color_palette( + diffused_error: &[f32; 3], + pixel: Rgb, + palette: &[Rgb], +) -> (Rgb, [f32; 3]) { + let mut dist_palette: Vec<[f32; 3]> = Vec::with_capacity(palette.len()); + + for color in palette { + dist_palette.push([ + color.0[0] as f32 - pixel.0[0] as f32 + diffused_error[0], + color.0[1] as f32 - pixel.0[1] as f32 + diffused_error[1], + (color.0[2] as f32) - pixel.0[2] as f32 + diffused_error[2], + ]); + } + + let index_of_min: Option = dist_palette + .iter() + .enumerate() + .min_by(|(_, x), (_, y)| { + (x[0].abs() + x[1].abs() + x[2].abs()) + .total_cmp(&(y[0].abs() + y[1].abs() + y[2].abs()).abs()) + }) + .map(|(index, _)| index); + + let i = index_of_min.unwrap() as usize; + + (palette[i], dist_palette[i]) +} +pub fn dither_palette(image: &mut RgbImage, palette: &[Rgb], kernel: kernel::Kernel) { + let (width, _height) = image.dimensions(); + let width = width as usize; + let error_buffer_size = kernel + .iter() + .map(|k| k.dx + k.dy * width as i32) + .max() + .unwrap() as usize; + assert!(error_buffer_size > 0); + let mut error: Vec<[f32; 3]> = vec![[0.; 3]; error_buffer_size]; + + for (x, y, pixel) in &mut image.enumerate_pixels_mut() { + let err_pos: u32 = (y * width as u32 + x) % (error_buffer_size as u32); + let (pixel_nearest, err) = chose_color_palette(&error[err_pos as usize], *pixel, palette); + + for e in &kernel { + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize][0] += err[0] as f32 * e.weight; + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize][1] += err[1] as f32 * e.weight; + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize][2] += err[2] as f32 * e.weight; + } + error[err_pos as usize] = [0., 0., 0.]; + + *pixel = pixel_nearest; + } +} + +fn l_rgb(p: f32) -> f32 { + if p < 0.04045 { + p * 0.0773993808 + } else { + f32::powf(p * 0.9478672986 + 0.0521327014, 2.4) + } +} +#[allow(dead_code)] +fn srgb(l: f32) -> f32 { + if l < 0.0031308 { + l * 12.92 + } else { + 1.055 * f32::powf(l, 0.41666) - 0.055 + } +} + +#[allow(dead_code)] +fn chose_color_palette_linear( + diffused_error: &[f32; 3], + pixel: Rgb, + palette: &[Rgb], +) -> (Rgb, [f32; 3]) { + let mut dist_palette: Vec<[f32; 3]> = Vec::with_capacity(palette.len()); + + // let distance_to_black = BLACK_LINEAR_SRGB.distance(grey_pixel).sqrt() + diffused_error; + //Linear = + // if (sRGB < 0.04045) sRGB * 0.0773993808 + // else (pow(sRGB * 0.9478672986 + 0.0521327014, 2.4)) + + for color in palette { + dist_palette.push([ + l_rgb(color.0[0] as f32) - l_rgb(pixel.0[0] as f32) + diffused_error[0], + l_rgb(color.0[1] as f32) - l_rgb(pixel.0[1] as f32) + diffused_error[1], + l_rgb(color.0[2] as f32) - l_rgb(pixel.0[2] as f32) + diffused_error[2], + ]); + } + + let index_of_min: Option = dist_palette + .iter() + .enumerate() + .min_by(|(_, x), (_, y)| { + (x[0].abs() + x[1].abs() + x[2].abs()) + .total_cmp(&(y[0].abs() + y[1].abs() + y[2].abs()).abs()) + }) + .map(|(index, _)| index); + + let i = index_of_min.unwrap() as usize; + + (palette[i], dist_palette[i]) +} +pub fn dither_palette_linear(image: &mut RgbImage, palette: &[Rgb], kernel: kernel::Kernel) { + let (width, _height) = image.dimensions(); + let width = width as usize; + let error_buffer_size = kernel + .iter() + .map(|k| k.dx + k.dy * width as i32) + .max() + .unwrap() as usize; + assert!(error_buffer_size > 0); + let mut error: Vec<[f32; 3]> = vec![[0.; 3]; error_buffer_size]; + + for (x, y, pixel) in &mut image.enumerate_pixels_mut() { + let err_pos: u32 = (y * width as u32 + x) % (error_buffer_size as u32); + let (pixel_nearest, err) = + chose_color_palette_linear(&error[err_pos as usize], *pixel, palette); + + for e in &kernel { + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize][0] += err[0] as f32 * e.weight; + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize][1] += err[1] as f32 * e.weight; + error[(err_pos as i32 + e.dx + width as i32 * e.dy).rem_euclid(error_buffer_size as i32) + as usize][2] += err[2] as f32 * e.weight; + } + error[err_pos as usize] = [0., 0., 0.]; + + *pixel = pixel_nearest; + } +} diff --git a/src/main.rs b/src/main.rs index 2595f78..e1ee198 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,80 +1,117 @@ // Implement a simple dithering algorithm -use image::{DynamicImage, ImageReader, Rgb, RgbImage}; -use std::env; -use std::io::Cursor; +use image::{ImageReader, Rgb}; +use std::path::Path; -fn dither_atkinson(image: &mut RgbImage) { - let (width, height) = image.dimensions(); - let mut error: Vec = vec![0.; (3 * width) as usize]; - - for (x, y, mut pixel_) in &mut image.enumerate_pixels_mut() { - let Rgb([r, g, b]) = pixel_; - let pixel = (*r as u32 + *g as u32 + *b as u32) as f32 / 3.; - let err_pos: u32 = (y * width + x) % (3 * width); - - let (pixel, err) = if (pixel + error[err_pos as usize]) > 127. { - (255, (pixel + error[err_pos as usize] - 255.) / 8.) - } else { - (0, (pixel + error[err_pos as usize]) / 8.) - }; - - error[err_pos as usize] = 0.; - error[((err_pos + 1) % (3 * width)) as usize] += err; - error[((err_pos + 2) % (3 * width)) as usize] += err; - error[((err_pos + width + 1) % (3 * width)) as usize] += err; - error[((err_pos + width - 1) % (3 * width)) as usize] += err; - error[((err_pos + width) % (3 * width)) as usize] += err; - error[((err_pos + 2 * width) % (3 * width)) as usize] += err; - - *pixel_ = image::Rgb([pixel, pixel, pixel]); - } -} - -fn dither_sierra(image: &mut RgbImage) { - let (width, height) = image.dimensions(); - let mut error: Vec = vec![0.; (3 * width) as usize]; - - for (x, y, mut pixel_) in &mut image.enumerate_pixels_mut() { - let Rgb([r, g, b]) = pixel_; - let pixel = (*r as u32 + *g as u32 + *b as u32) as f32 / 3.; - let err_pos: u32 = (y * width + x) % (3 * width); - - let (pixel, err) = if (pixel + error[err_pos as usize]) > 127. { - (255, (pixel + error[err_pos as usize] - 255.) / 32.) - } else { - (0, (pixel + error[err_pos as usize]) / 32.) - }; - - error[err_pos as usize] = 0.; - error[((err_pos + 1) % (3 * width)) as usize] += 5. * err; - error[((err_pos + 2) % (3 * width)) as usize] += 3. * err; - error[((err_pos + width + 2) % (3 * width)) as usize] += 2. * err; - error[((err_pos + width + 1) % (3 * width)) as usize] += 4. * err; - error[((err_pos + width) % (3 * width)) as usize] += 5. * err; - error[((err_pos + width - 1) % (3 * width)) as usize] += 4. * err; - error[((err_pos + width - 2) % (3 * width)) as usize] += 2. * err; - error[((err_pos + 2 * width + 1) % (3 * width)) as usize] += 2. * err; - error[((err_pos + 2 * width) % (3 * width)) as usize] += 3. * err; - error[((err_pos + 2 * width - 1) % (3 * width)) as usize] += 2. * err; - - *pixel_ = image::Rgb([pixel, pixel, pixel]); - } -} +const LIST_OF_SAMPLES: [&str; 7] = [ + "samples/david.png", + "samples/david2.jpg", + "samples/us.png", + "samples/jane.jpg", + "samples/jane2.webp", + "samples/karel_käos.jpg", + "samples/diderot_111.png", +]; +const PALETTE_3B: [Rgb; 8] = [ + Rgb([0, 0, 255]), + Rgb([0, 255, 255]), + Rgb([255, 0, 255]), + Rgb([255, 255, 255]), + Rgb([0, 0, 0]), + Rgb([0, 255, 0]), + Rgb([255, 0, 0]), + Rgb([255, 255, 0]), +]; fn main() -> Result<(), image::ImageError> { + /* let args: Vec = env::args().collect(); - let image_path = format!("{}.png", &args[1]); - let image_out = format!("{}_dithered_atkinso.jpg", &args[1]); - let image_out_sierra = format!("{}_dithered_sierra.jpg", &args[1]); + */ - let mut img = ImageReader::open(&image_path)?.decode()?.into_rgb8(); - dither_atkinson(&mut img); - img.save(image_out)?; + let algos: Vec<(&str, dither::DitherFn)> = vec![ + // ("dither_kong_kui_1b", dither::dither_kong_kui), + // ("dither_atkinson_linear", dither::dither_atkinson_linear), + // ("kernel_atkinson_linear", |img| { + // dither::dither_linear(img, dither::kernel::ATKINSON_KERNEL.to_vec()) + // }), + ("kernel_atkinson_palette_3b", |img| { + dither::dither_palette(img, &PALETTE_3B, dither::kernel::ATKINSON_KERNEL.to_vec()) + }), + ("kernel_jarvis_judice_ninke_linear_3b", |img| { + dither::dither_palette_linear( + img, + &PALETTE_3B, + dither::kernel::JARVIS_JUDICE_NINKE_KERNEL.to_vec(), + ) + }), + /* + ("kernel_kong_kui_3b", |img| { + dither::dither_shift_8colors( + img, + dither::kernel::KernelShift { + kernel: dither::kernel::KONG_KUI_KERNEL_INTEGER.to_vec(), + bit_shift: dither::kernel::KONG_KUI_KERNEL_BIT_SHIFT, + }, + ) + }), + ("random_1b", dither::dither_random), + ("random_3b", dither::dither_random_color), + ("threshold_1b", dither::dither_threshold_1b), + ("threshold_3b", dither::dither_threshold_3b), + ("burkes_3b", |img| { + dither::dither_shift_8colors( + img, + dither::kernel::KernelShift { + kernel: dither::kernel::BURKES_KERNEL_INTEGER.to_vec(), + bit_shift: dither::kernel::BURKES_KERNEL_BIT_SHIFT, + }, + ) + }),*/ + ]; - let mut img = ImageReader::open(&image_path)?.decode()?.into_rgb8(); - dither_sierra(&mut img); - img.save(image_out_sierra)?; + for sample in LIST_OF_SAMPLES { + for (suffix, algo) in &algos { + println!("{} on {}", suffix, sample); + let path = Path::new(sample); + let mut img = ImageReader::open(&sample)?.decode()?.into_rgb8(); + algo(&mut img); + let path_out = format!( + "{}/{}_dithered_{}.png", + path.parent().unwrap().to_str().unwrap(), + path.file_stem().unwrap().to_str().unwrap(), + suffix + ); + img.save(&path_out)?; + } + } Ok(()) } + +// https://github.com/blenderskool/pigmnts/tree/master +// https://github.com/cyotek/Dithering/blob/master/resources/DITHER.TXT +// https://github.com/cyotek/Dithering/blob/master/resources/DHALF.TXT +// https://cv.ulichney.com/papers/1993-void-cluster.pdf +// https://en.wikipedia.org/wiki/Ordered_dithering +// https://www.sciencedirect.com/science/article/abs/pii/014193829190005X +// https://www.google.fr/search?q=Ostromoukhov+dithering+matrix&sca_esv=cc91aa7b516a412e&ei=xxgIaKe5LKLekPIP876PuQY&ved=0ahUKEwjnuPbg2OyMAxUiL0QIHXPfI2cQ4dUDCBI&uact=5&oq=Ostromoukhov+dithering+matrix&gs_lp=Egxnd3Mtd2l6LXNlcnAiHU9zdHJvbW91a2hvdiBkaXRoZXJpbmcgbWF0cml4MgUQABjvBTIFEAAY7wUyBRAAGO8FMgUQABjvBTIFEAAY7wVIrMEBUN-rAVj-vgFwAngAkAEAmAH6AqABwQ2qAQUyLTYuMbgBA8gBAPgBAZgCCaAC8g3CAgcQABiwAxgewgILEAAYgAQYsAMYogTCAggQABiwAxjvBcICBhAAGBYYHsICCBAAGIAEGKIEwgIFECEYoAHCAgcQIRigARgKmAMAiAYBkAYGkgcHMi4wLjYuMaAHlBGyBwUyLTYuMbgH5g0&sclient=gws-wiz-serp +// +// https://perso.liris.cnrs.fr/ostrom/publications/pdf/SIGGRAPH99_MultiColorDithering_600dpi.pdf +// https://patents.google.com/patent/EP0828379A2/en +// https://www.iro.umontreal.ca/~ostrom/publications/pdf/SIGGRAPH01_varcoeffED.pdf +// https://observablehq.com/@jobleonard/variable-coefficient-dithering +// https://perso.liris.cnrs.fr/victor.ostromoukhov/publications/pdf/SIGGRAPH94_RotatedDither.pdf +// https://perso.liris.cnrs.fr/victor.ostromoukhov/publications/pdf/SIGGRAPH99_MultiColorDithering_600dpi.pdf +// https://perso.liris.cnrs.fr/victor.ostromoukhov/publications/SIGAsisa2024_Best_paper_award.jpg +// https://bisqwit.iki.fi/story/howto/dither/jy/ +// https://github.com/samhocevar/scolorq/blob/master/spatial_color_quant.cpp +// https://github.com/samhocevar/scolorq +// https://patents.google.com/patent/EP0828379A2/en +// https://lib.rs/crates/rscolorq +// https://github.com/okaneco/rscolorq +// https://people.eecs.berkeley.edu/~dcoetzee/downloads/scolorq/ +// https://web.archive.org/web/20210625055243/https://people.eecs.berkeley.edu/~dcoetzee/downloads/scolorq/#contact +// https://newsgroup.xnview.com/viewtopic.php?t=16591 +// https://bisqwit.iki.fi/jutut/colorquant/ +// https://github.com/tromero/BayerMatrix +// https://docs.gimp.org/2.10/en/gimp-filter-bayer-matrix.html diff --git a/src/test_all.rs b/src/test_all.rs new file mode 100644 index 0000000..0d96ede --- /dev/null +++ b/src/test_all.rs @@ -0,0 +1,147 @@ +// Implement a simple dithering algorithm +use image::ImageReader; +use std::path::Path; + +const LIST_OF_SAMPLES: [&str; 7] = [ + "samples/david.png", + "samples/david2.jpg", + "samples/us.png", + "samples/diderot_111.png", + "samples/jane.jpg", + "samples/jane2.webp", + "samples/karel_käos.jpg", +]; + +fn main() -> Result<(), image::ImageError> { + /* + let args: Vec = env::args().collect(); + let image_path = format!("{}.png", &args[1]); + */ + + let algos: Vec<(&str, dither::DitherFn)> = vec![ + ("atkinson", dither::dither_atkinson), + ("atkinson_base", dither::dither_atkinson_base), + ("atkinson_linear", dither::dither_atkinson_linear), + ("atkinson_linear_sqrt", dither::dither_atkinson_linear_sqrt), + ("kernel_atkinson", |img| { + dither::dither(img, dither::kernel::ATKINSON_KERNEL.to_vec()) + }), + ("kernel_atkinson_integer", |img| { + dither::dither_shift( + img, + dither::kernel::KernelShift { + kernel: dither::kernel::ATKINSON_KERNEL_INTEGER.to_vec(), + bit_shift: dither::kernel::ATKINSON_KERNEL_BIT_SHIFT, + }, + ) + }), + ("kernel_floyd_steinberg", |img| { + dither::dither(img, dither::kernel::FLOYD_STEINBERG_KERNEL.to_vec()) + }), + ("kernel_floyd_steinberg_integer", |img| { + dither::dither_shift( + img, + dither::kernel::KernelShift { + kernel: dither::kernel::FLOYD_STEINBERG_KERNEL_INTEGER.to_vec(), + bit_shift: dither::kernel::FLOYD_STEINBERG_KERNEL_BIT_SHIFT, + }, + ) + }), + ("kernel_fedoseev", |img| { + dither::dither(img, dither::kernel::FEDOSEEV_KERNEL.to_vec()) + }), + ("kernel_fedoseev2", |img| { + dither::dither(img, dither::kernel::FEDOSEEV2_KERNEL.to_vec()) + }), + ("kernel_fedoseev3", |img| { + dither::dither(img, dither::kernel::FEDOSEEV3_KERNEL.to_vec()) + }), + ("kernel_fedoseev4", |img| { + dither::dither(img, dither::kernel::FEDOSEEV4_KERNEL.to_vec()) + }), + ("kernel_wong_allebach", |img| { + dither::dither(img, dither::kernel::WONG_ALLEBACH_KERNEL.to_vec()) + }), + ("kernel_jarvis_judice_ninke", |img| { + dither::dither(img, dither::kernel::JARVIS_JUDICE_NINKE_KERNEL.to_vec()) + }), + ("kernel_stucki", |img| { + dither::dither(img, dither::kernel::STUCKI_KERNEL.to_vec()) + }), + ("kernel_burkes", |img| { + dither::dither(img, dither::kernel::BURKES_KERNEL.to_vec()) + }), + ("kernel_burkes_integer", |img| { + dither::dither_shift( + img, + dither::kernel::KernelShift { + kernel: dither::kernel::BURKES_KERNEL_INTEGER.to_vec(), + bit_shift: dither::kernel::BURKES_KERNEL_BIT_SHIFT, + }, + ) + }), + ("kernel_sierra3", |img| { + dither::dither(img, dither::kernel::SIERRA3_KERNEL.to_vec()) + }), + ("kernel_sierra3_integer", |img| { + dither::dither_shift( + img, + dither::kernel::KernelShift { + kernel: dither::kernel::SIERRA3_KERNEL_INTEGER.to_vec(), + bit_shift: dither::kernel::SIERRA3_KERNEL_BIT_SHIFT, + }, + ) + }), + ("kernel_sierra2", |img| { + dither::dither(img, dither::kernel::SIERRA2_KERNEL.to_vec()) + }), + ("kernel_sierra2_integer", |img| { + dither::dither_shift( + img, + dither::kernel::KernelShift { + kernel: dither::kernel::SIERRA2_KERNEL_INTEGER.to_vec(), + bit_shift: dither::kernel::SIERRA2_KERNEL_BIT_SHIFT, + }, + ) + }), + ("kernel_sierra-lite", |img| { + dither::dither(img, dither::kernel::SIERRA_LITE_KERNEL.to_vec()) + }), + ("kernel_sierra-line_integer", |img| { + dither::dither_shift( + img, + dither::kernel::KernelShift { + kernel: dither::kernel::SIERRA_LITE_KERNEL_INTEGER.to_vec(), + bit_shift: dither::kernel::SIERRA_LITE_KERNEL_BIT_SHIFT, + }, + ) + }), + ("kernel_hallucination_prewitt", |img| { + dither::dither(img, dither::kernel::PREWITT_KERNEL.to_vec()) + }), + ("kernel_hallucination_roberts", |img| { + dither::dither(img, dither::kernel::ROBERTS_KERNEL.to_vec()) + }), + ("kernel_hallucination_sobel", |img| { + dither::dither(img, dither::kernel::SOBEL_KERNEL.to_vec()) + }), + ]; + + for sample in LIST_OF_SAMPLES { + for (suffix, algo) in &algos { + println!("{} on {}", suffix, sample); + let path = Path::new(sample); + let mut img = ImageReader::open(&sample)?.decode()?.into_rgb8(); + algo(&mut img); + let path_out = format!( + "{}/{}_dithered_{}.png", + path.parent().unwrap().to_str().unwrap(), + path.file_stem().unwrap().to_str().unwrap(), + suffix + ); + img.save(&path_out)?; + } + } + + Ok(()) +} -- cgit v1.3-2-g11bf