dither_lib/
lib.rs

1//! This is a simple dithering library
2//!
3//! # Usage
4//! ```rust
5//! pub fn main(){
6//! let image: DynamicImage = todo!();
7//! let dithered_image = DitherBuilder::new(image)
8//!     .highlights(Rgb([255;3]))
9//!     .shadows(Rgb([0;3]))
10//!     .resize(Resize::Scale(0.5))
11//!     .generate();
12//!}
13//!```
14//re-export `image`'s `Rgb<_>` struct
15pub 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
24/// Dithered image builder
25pub 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    /// Initializes a new `DitherBuilder`
35    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    /// Sets the dithering level
56    pub fn level(mut self, level: u8) -> Self {
57        self.level = level;
58        self
59    }
60    /// Resizes the output image
61    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    /// Sets the color of highlights in the dithered image
76    pub fn highlights(mut self, highlights: Rgb<u8>) -> Self {
77        self.highlights = highlights;
78        self
79    }
80    /// Sets the color of the shadows in the dithered image
81    pub fn shadows(mut self, shadows: Rgb<u8>) -> Self {
82        self.shadows = shadows;
83        self
84    }
85    /// Generate a dithered image given a set of parameters and returns a DynamicImage
86    pub fn generate(self) -> DynamicImage {
87        //generate equalizer
88        let num = 2_u8.pow(self.level.into());
89        let equalizer = 1. / (num as f32).powf(2.);
90        //generate bayer layer
91        let bayer_layer = generate_bayer(self.level).mapv(|x| (x as f32) * equalizer);
92        //convert to grayscale
93        let image = self.image.grayscale();
94        //resize image
95        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}