Skip to main content

image_effects/dither/
error.rs

1use std::marker::PhantomData;
2
3
4use palette::Srgb;
5
6use crate::{
7    utils::{image::{get_dimensions_of_matrix, RgbImageRepr}},
8    colour::utils::{quantize_rgb, compute_rgb_error}, effect::Effect,
9};
10
11/// Every `ErrorPropagator` starts with a state of `Base`.
12/// 
13/// Base `ErrorPropagator`s _cannot_ be used as an `Effect`,
14/// as they require a palette. This can be done with `.with_palette`.
15pub struct Base;
16
17/// Once an `ErrorPropagator` acquires a colour palette, it enters the `WithPalette` state.
18/// 
19/// With this state, it can now be used as an effect.
20pub struct WithPalette;
21
22mod private {
23    pub trait Sealed {}
24
25    impl Sealed for super::Base {}
26    impl Sealed for super::WithPalette {}
27}
28
29/// Note: This trait is *sealed* and should not be used externally.
30/// 
31/// It is used specifically as a type-state for an `ErrorPropagator`.
32pub trait PropagatorState: private::Sealed {}
33impl PropagatorState for Base {}
34impl PropagatorState for WithPalette {}
35
36/// This struct defines an error propagation algorithm. For existing algorithms, the constants should be used instead.
37/// 
38/// An `ErrorPropagator` doesn't start out as an effect, as it requires a colour palette to actually perform the dithering.
39/// 
40/// This can be done by simply calling `.with_palette`, which will generate a configured version of the propagator.
41pub struct ErrorPropagator<'name, 'matrix, S: PropagatorState> {
42    /// The name of the algorithm in question.
43    pub name: &'name str,
44
45    /// The error propagation matrix, in the form of (dx, dy, portion).
46    /// 
47    /// For example, (1, 0, 1) will propagate `1/portion` of the error
48    /// to the next pixel on the right.
49    pub matrix: &'matrix [(i8, i8, u8)],
50
51    /// The total amount of portions. Note that this doesn't need to 
52    /// equal the sum of portions in the `matrix`. 
53    /// 
54    /// For example, Atkinson doesn't distribute all portions - and 
55    /// one could theoretically _over-propagate_ by distributing more
56    /// portions in the `matrix` than listed here.
57    pub portions: u16,
58
59    /// The colour palette that the error propagator has been configured with.
60    /// Required to function as an effect.
61    palette: Option<Vec<Srgb>>,
62
63    /// Phantom data to own the state.
64    _phantom: PhantomData<S>,
65}
66
67impl<'a, 'b> ErrorPropagator<'a, 'b, Base> {
68    pub const fn new(name: &'a str, matrix: &'b [(i8, i8, u8)], portions: u16) -> Self {
69        ErrorPropagator {
70            name,
71            matrix,
72            portions,
73            palette: None,
74            _phantom: PhantomData,
75        }
76    }
77}
78
79impl<'a, 'b, S: PropagatorState> ErrorPropagator<'a, 'b, S> {
80    pub fn with_palette(&self, palette: Vec<Srgb>) -> ErrorPropagator<'a, 'b, WithPalette> {
81        ErrorPropagator {
82            name: self.name,
83            matrix: self.matrix,
84            portions: self.portions,
85            palette: Some(palette),
86            _phantom: PhantomData,
87        }
88    }
89}
90
91impl<'a, 'b> Effect<RgbImageRepr> for ErrorPropagator<'a, 'b, WithPalette> {
92    fn affect(&self, mut image: RgbImageRepr) -> RgbImageRepr {
93        let (xdim, ydim) = get_dimensions_of_matrix(&image);
94
95        if xdim == 0 || ydim == 0 {
96            return image;
97        }
98
99        for y in 0..ydim {
100            for x in 0..xdim {
101                let error = {
102                    let rgb = Srgb::from(image[y][x]).into_format::<f32>();
103                    let quantized = quantize_rgb(rgb, self.palette.as_ref().unwrap());
104                    image[y][x] = quantized.into_format().into();
105                    compute_rgb_error(rgb, quantized)
106                };
107
108                let error = (
109                    error.0 * 255.0,
110                    error.1 * 255.0,
111                    error.2 * 255.0,
112                );
113
114                for (x_off, y_off, portion) in self.matrix.iter() {
115                    let (x_err, y_err) = (
116                        (x as i64 + *x_off as i64) as usize,
117                        (y as i64 + *y_off as i64) as usize,
118                    );
119
120                    let pixel = image
121                        .get_mut(y_err as usize)
122                        .and_then(|row| row.get_mut(x_err as usize));
123
124                    if let Some(pixel) = pixel {
125                        *pixel = [
126                            (pixel[0] as f32 + (error.0 * *portion as f32) / self.portions as f32).clamp(0.0, 255.0) as u8,
127                            (pixel[1] as f32 + (error.1 * *portion as f32) / self.portions as f32).clamp(0.0, 255.0) as u8,
128                            (pixel[2] as f32 + (error.2 * *portion as f32) / self.portions as f32).clamp(0.0, 255.0) as u8,
129                        ]
130                    }
131                }
132            }
133        }
134        image
135    }
136}
137
138type ConstErrorPropagator = ErrorPropagator<'static, 'static, Base>;
139
140/// The Floyd-Steinberg error propagation method.
141/// 
142/// Distributes the entire error.
143/// 
144/// ```notrust
145/// - x 7
146/// 5 3 1
147/// ```
148pub const FLOYD_STEINBERG: ConstErrorPropagator = ErrorPropagator::new(
149    "floyd-steinberg",
150    &[
151                            (1, 0, 7),
152       (-1, 1, 5),(0, 1, 3),(1, 1, 1),
153    ],
154    16,
155);
156
157/// The Jarvis-Judice-Ninke error propagation method.
158/// 
159/// Distributes the entire error.
160/// 
161/// ```notrust
162/// - - x 7 5
163/// 3 5 7 5 3
164/// 1 3 5 3 1
165/// ```
166pub const JARVIS_JUDICE_NINKE: ConstErrorPropagator = ErrorPropagator::new(
167    "jarvis-judice-ninke",
168    &[
169                                        (1, 0, 7),(2, 0, 5),
170        (-2, 1, 3),(-1, 1, 5),(0, 1, 7),(1, 1, 5),(2, 1, 3),
171        (-2, 2, 1),(-1, 2, 3),(0, 2, 5),(1, 2, 3),(2, 2, 1),
172    ],
173    48,
174);
175
176/// The Atkinson error propagation method.
177/// 
178/// Doesn't distribute the entire error - only 6/8ths of it.
179/// 
180/// ```notrust
181/// - - x 1 1
182/// - 1 1 1 -
183/// - - 1 - -
184/// ```
185pub const ATKINSON: ConstErrorPropagator = ErrorPropagator::new(
186    "atkinson",
187    &[
188                             (1, 0, 1),(2, 0, 1),
189        (-1, 1, 1),(0, 1, 1),(1, 1, 1),
190                   (0, 2, 1)
191    ],
192    8,
193);
194
195/// The Burkes error propagation method.
196/// 
197/// Distributes the entire error.
198/// 
199/// ```notrust
200/// - - x 8 4
201/// 2 4 8 4 2
202/// ```
203pub const BURKES: ConstErrorPropagator = ErrorPropagator::new(
204    "burkes",
205    &[
206                                           (1, 0, 8), (2, 0, 4),
207        (-2, 1, 2), (-1, 1, 4), (0, 1, 8), (1, 1, 4), (2, 1, 2),
208    ],
209    32,
210);
211
212/// The Stucki error propagation method.
213/// 
214/// Distributes the entire error.
215/// 
216/// ```notrust
217/// - - x 8 4
218/// 2 4 8 4 2
219/// 1 2 4 2 1
220/// ```
221pub const STUCKI: ConstErrorPropagator = ErrorPropagator::new(
222    "stucki",
223    &[
224                                        (1, 0, 8),(2, 0, 4),
225        (-2, 1, 2),(-1, 1, 4),(0, 1, 8),(1, 1, 4),(2, 1, 2),
226        (-2, 2, 1),(-1, 2, 2),(0, 2, 4),(1, 2, 2),(2, 2, 1),
227    ],
228    42,
229);
230
231/// The Sierra error propagation method.
232/// 
233/// Distributes the entire error.
234/// 
235/// ```notrust
236/// - - x 5 3
237/// 2 4 5 4 2
238/// - 2 3 2 -
239/// ```
240pub const SIERRA: ConstErrorPropagator = ErrorPropagator::new(
241    "sierra",
242    &[
243                                        (1, 0, 5),(2, 0, 3),
244        (-2, 1, 2),(-1, 1, 4),(0, 1, 5),(1, 1, 4),(2, 1, 2),
245                   (-1, 2, 2),(0, 2, 3),(1, 2, 2),
246    ],
247    32,
248);
249
250/// The Sierra (Two Row) error propagation method. A variant of Sierra.
251/// 
252/// Distributes the entire error.
253/// 
254/// ```notrust
255/// - - x 4 3
256/// 1 2 3 2 1
257/// ```
258pub const SIERRA_TWO_ROW: ConstErrorPropagator = ErrorPropagator::new(
259    "sierra-two-row",
260    &[
261                                        (1, 0, 4),(2, 0, 3),
262        (-2, 1, 1),(-1, 1, 2),(0, 1, 3),(1, 1, 2),(2, 1, 1),
263    ],
264    16,
265);
266
267/// The Sierra (lite) error propagation method. A variant of Sierra.
268/// 
269/// Distributes the entire error.
270/// 
271/// ```notrust
272/// - x 2
273/// 1 1 -
274/// ```
275pub const SIERRA_LITE: ConstErrorPropagator = ErrorPropagator::new(
276    "sierra-lite",
277    &[
278                            (1, 0, 2),
279        (-1, 1, 1),(0, 1, 1)
280    ],
281    4,
282);