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);