1pub use image::Rgb;
16use image::{imageops::FilterType, DynamicImage};
17use ndarray::{arr2, concatenate, Array2, Axis};
18use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
19
20const R_CHANNEL_MULTIPLIER: f64 = 0.2126;
21const G_CHANNEL_MULTIPLIER: f64 = 0.7152;
22const B_CHANNEL_MULTIPLIER: f64 = 0.0722;
23
24pub struct DitherBuilder {
26 image: DynamicImage,
27 level: u8,
28 width: u32,
29 height: u32,
30 shadows: Rgb<u8>,
31 highlights: Rgb<u8>,
32}
33impl DitherBuilder {
34 pub fn new(image: DynamicImage) -> DitherBuilder {
36 let width = image.width();
37 let height = image.height();
38 DitherBuilder {
39 image,
40 width,
41 height,
42 shadows: Rgb([0; 3]),
43 highlights: Rgb([255; 3]),
44 level: 2,
45 }
46 }
47}
48
49pub enum Resize {
50 Scale(f32),
51 Resolution { width: u32, height: u32 },
52}
53
54impl DitherBuilder {
55 pub fn level(mut self, level: u8) -> Self {
57 self.level = level;
58 self
59 }
60 pub fn resize(mut self, resize: Resize) -> Self {
62 match resize {
63 Resize::Scale(scale) => {
64 self.width = (scale * self.width as f32) as u32;
65 self.height = (scale * self.height as f32) as u32;
66 }
67 Resize::Resolution { width, height } => {
68 self.width = width;
69 self.height = height;
70 }
71 };
72 self
73 }
74
75 pub fn highlights(mut self, highlights: Rgb<u8>) -> Self {
77 self.highlights = highlights;
78 self
79 }
80 pub fn shadows(mut self, shadows: Rgb<u8>) -> Self {
82 self.shadows = shadows;
83 self
84 }
85 pub fn generate(self) -> DynamicImage {
87 let num = 2_u8.pow(self.level.into());
89 let equalizer = 1. / (num as f32).powf(2.);
90 let bayer_layer = generate_bayer(self.level).mapv(|x| (x as f32) * equalizer);
92 let image = self.image.grayscale();
94 let image = image.resize(self.width, self.height, FilterType::Nearest);
96 let mut binding = image.to_rgb8();
97 let mut image_buffer: Vec<_> = binding.enumerate_pixels_mut().collect();
98 image_buffer
99 .par_iter_mut()
100 .enumerate()
101 .for_each(|(_, (x, y, pixel))| {
102 let bayer_len = bayer_layer.shape()[0];
103 let x = x.rem_euclid(bayer_len.try_into().unwrap()) as usize;
104 let y = y.rem_euclid(bayer_len.try_into().unwrap()) as usize;
105 let pixel_brightness = pixel_brightness(pixel);
106 if pixel_brightness > (1. - bayer_layer[[y, x]]).into() {
107 set_pixel(*pixel, self.highlights);
108 } else {
109 set_pixel(*pixel, self.shadows);
110 }
111 });
112
113 let mut new_img_buffer = DynamicImage::new_rgb8(self.width, self.height).to_rgb8();
114 for (x, y, pixel) in image_buffer {
115 new_img_buffer.put_pixel(x, y, *pixel);
116 }
117 DynamicImage::ImageRgb8(new_img_buffer)
118 }
119}
120
121fn pixel_brightness(pixel: &Rgb<u8>) -> f64 {
122 let r = pixel[0] as f64 / 255.;
123 let g = pixel[1] as f64 / 255.;
124 let b = pixel[2] as f64 / 255.;
125
126 let pixel_brightness =
127 r * R_CHANNEL_MULTIPLIER + g * G_CHANNEL_MULTIPLIER + b * B_CHANNEL_MULTIPLIER;
128 gamma_correct(pixel_brightness)
129}
130
131fn set_pixel(pixel: &mut Rgb<u8>, color: Rgb<u8>) {
132 pixel[0] = color[0];
133 pixel[1] = color[1];
134 pixel[2] = color[2];
135}
136
137fn generate_bayer(level: u8) -> Array2<i32> {
138 let num = 2_u8.pow(level.into());
139 match num {
140 2 => arr2(&[[0, 2], [3, 1]]),
141 _ => {
142 concatenate![
143 Axis(0),
144 concatenate![
145 Axis(1),
146 4 * generate_bayer(level - 1),
147 4 * generate_bayer(level - 1) + 2
148 ],
149 concatenate![
150 Axis(1),
151 4 * generate_bayer(level - 1) + 3,
152 4 * generate_bayer(level - 1) + 1
153 ]
154 ]
155 }
156 }
157}
158
159fn gamma_correct(pixel_brightness: f64) -> f64 {
160 if pixel_brightness <= 0.0405 {
161 pixel_brightness / 12.92
162 } else {
163 (pixel_brightness + 0.055 / 1.055).powf(2.4)
164 }
165}