a_sixel/
dither.rs

1//! A collection of various dithering algorithms that can be used with the
2//! [`SixelEncoder`](crate::SixelEncoder) to dither the result.
3//!
4//! Most of the dithering algorithms are based on the ones described in
5//! [this article](https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html).
6//! There is also an implementation of the Bayer matrix dithering algorithm
7//! and a Sobol sequence based dithering algorithm.
8
9use dilate::DilateExpand;
10use image::RgbImage;
11use kiddo::float::kdtree::KdTree;
12use palette::Lab;
13use rayon::{
14    iter::{
15        IndexedParallelIterator,
16        IntoParallelIterator,
17        ParallelIterator,
18    },
19    slice::ParallelSliceMut,
20};
21use sobol_burley::sample_4d;
22
23use crate::{
24    private,
25    rgb_to_lab,
26};
27
28/// Each struct in this module implements this trait and can be combined with
29/// the [`SixelEncoder`](crate::SixelEncoder) struct to dither the result.
30pub trait Dither: private::Sealed {
31    const KERNEL: &[(isize, isize, f32)];
32    const DIV: f32;
33
34    /// Take the input image and convert it to the input palette, applying a
35    /// dithering algorithm to the result.
36    ///
37    /// `target_palette_size` indicates the size of the palette that was
38    /// targetted during palette creation. This is used by some algorithms e.g.
39    /// Bayer to determine the error level for dithering. It should be `>=
40    /// in_palette.len()`.
41    fn dither_and_palettize(
42        image: &RgbImage,
43        in_palette: &[Lab],
44        target_palette_size: usize,
45    ) -> Vec<usize> {
46        let _ = target_palette_size;
47
48        let pixels = image.pixels().copied().map(rgb_to_lab).collect::<Vec<_>>();
49
50        let mut palette = KdTree::<_, _, 3, 257, u32>::with_capacity(in_palette.len());
51
52        for (idx, color) in in_palette.iter().enumerate() {
53            palette.add(color.as_ref(), idx);
54        }
55
56        let mut result = vec![0; image.width() as usize * image.height() as usize];
57
58        let mut spills = vec![[0.0; 3]; image.width() as usize * image.height() as usize];
59
60        for (idx, p) in pixels.iter().copied().enumerate() {
61            let pixel = <Lab>::new(
62                p.l + spills[idx][0],
63                p.a + spills[idx][1],
64                p.b + spills[idx][2],
65            );
66
67            let palette_idx =
68                palette.nearest_one::<kiddo::SquaredEuclidean>(&[pixel.l, pixel.a, pixel.b]);
69
70            let error0 = pixel.l - in_palette[palette_idx.item].l;
71            let error1 = pixel.a - in_palette[palette_idx.item].a;
72            let error2 = pixel.b - in_palette[palette_idx.item].b;
73            let spill = [error0, error1, error2];
74
75            result[idx] = palette_idx.item;
76
77            for (dx, dy, m) in Self::KERNEL {
78                let x = idx as isize % image.width() as isize + dx;
79                let y = idx as isize / image.width() as isize + dy;
80                if x < 0 || y < 0 || x >= image.width() as isize || y >= image.height() as isize {
81                    continue;
82                }
83
84                let jdx = y * image.width() as isize + x;
85                let target = &mut spills[jdx as usize];
86                *target = [
87                    target[0] + (spill[0] * m) / Self::DIV / 2.0,
88                    target[1] + (spill[1] * m) / Self::DIV / 2.0,
89                    target[2] + (spill[2] * m) / Self::DIV / 2.0,
90                ];
91            }
92        }
93
94        result
95    }
96}
97
98/// Do not perform dithering. This is the fastest possible palette encoding
99/// option.
100pub struct NoDither;
101impl private::Sealed for NoDither {}
102impl Dither for NoDither {
103    const DIV: f32 = 1.0;
104    const KERNEL: &[(isize, isize, f32)] = &[];
105
106    fn dither_and_palettize(image: &RgbImage, in_palette: &[Lab], _: usize) -> Vec<usize> {
107        let pixels = image.pixels().copied().map(rgb_to_lab).collect::<Vec<_>>();
108
109        let mut palette = KdTree::<_, _, 3, 32, u32>::with_capacity(in_palette.len());
110
111        for (idx, color) in in_palette.iter().enumerate() {
112            palette.add(color.as_ref(), idx);
113        }
114
115        let mut result = vec![0; image.width() as usize * image.height() as usize];
116        pixels
117            .into_par_iter()
118            .zip(&mut result)
119            .for_each(|(p, dest)| {
120                *dest = palette
121                    .nearest_one::<kiddo::SquaredEuclidean>(&[p.l, p.a, p.b])
122                    .item;
123            });
124
125        result
126    }
127}
128
129pub struct Sierra;
130impl private::Sealed for Sierra {}
131impl Dither for Sierra {
132    const DIV: f32 = 32.0;
133    // _ _ X 5 3
134    // 2 4 5 4 2
135    // _ 2 3 2 _
136    const KERNEL: &[(isize, isize, f32)] = &[
137        (1, 0, 5.0),
138        (2, 0, 3.0),
139        //
140        (-2, 1, 2.0),
141        (-1, 1, 4.0),
142        (0, 1, 5.0),
143        (1, 1, 4.0),
144        (2, 1, 2.0),
145        //
146        (-1, 2, 2.0),
147        (0, 2, 3.0),
148        (1, 2, 2.0),
149    ];
150}
151
152pub struct Sierra2;
153impl private::Sealed for Sierra2 {}
154impl Dither for Sierra2 {
155    const DIV: f32 = 16.0;
156    // _ _ X 4 3
157    // 1 2 3 2 1
158    // _ 1 2 1 _
159    const KERNEL: &[(isize, isize, f32)] = &[
160        //x y  m
161        (1, 0, 4.0),
162        (2, 0, 3.0),
163        //
164        (-2, 1, 1.0),
165        (-1, 1, 2.0),
166        (0, 1, 3.0),
167        (1, 1, 2.0),
168        (2, 1, 1.0),
169    ];
170}
171
172pub struct SierraLite;
173impl private::Sealed for SierraLite {}
174impl Dither for SierraLite {
175    const DIV: f32 = 4.0;
176    //  _ X 2
177    //  1 1 _
178    const KERNEL: &[(isize, isize, f32)] = &[
179        //x y  m
180        (1, 0, 2.0),
181        //
182        (-1, 1, 1.0),
183        (0, 1, 1.0),
184    ];
185}
186
187pub struct FloydSteinberg;
188impl private::Sealed for FloydSteinberg {}
189impl Dither for FloydSteinberg {
190    const DIV: f32 = 16.0;
191    // _ X 7
192    // 3 5 3
193    const KERNEL: &[(isize, isize, f32)] = &[
194        //x y  m
195        (1, 0, 7.0),
196        //
197        (-1, 1, 3.0),
198        (0, 1, 5.0),
199        (1, 1, 1.0),
200    ];
201}
202
203pub struct JJN;
204impl private::Sealed for JJN {}
205impl Dither for JJN {
206    const DIV: f32 = 48.0;
207    // _ _ X 7 5
208    // 3 5 7 5 3
209    // 1 2 5 3 1
210    const KERNEL: &[(isize, isize, f32)] = &[
211        //x y  m
212        (1, 0, 7.0),
213        (2, 0, 5.0),
214        //
215        (-2, 1, 3.0),
216        (-1, 1, 5.0),
217        (0, 1, 7.0),
218        (1, 1, 5.0),
219        (2, 1, 3.0),
220        //
221        (-2, 2, 1.0),
222        (-1, 2, 2.0),
223        (0, 2, 5.0),
224        (1, 2, 3.0),
225        (2, 2, 1.0),
226    ];
227}
228
229pub struct Stucki;
230impl private::Sealed for Stucki {}
231impl Dither for Stucki {
232    const DIV: f32 = 42.0;
233    // _ _ X 8 4
234    // 2 4 8 4 2
235    // 1 2 4 2 1
236    const KERNEL: &[(isize, isize, f32)] = &[
237        //x y  m
238        (1, 0, 8.0),
239        (2, 0, 4.0),
240        //
241        (-2, 1, 2.0),
242        (-1, 1, 4.0),
243        (0, 1, 8.0),
244        (1, 1, 4.0),
245        (2, 1, 2.0),
246        //
247        (-2, 2, 1.0),
248        (-1, 2, 2.0),
249        (0, 2, 4.0),
250        (1, 2, 2.0),
251        (2, 2, 1.0),
252    ];
253}
254
255pub struct Atkinson;
256impl private::Sealed for Atkinson {}
257impl Dither for Atkinson {
258    const DIV: f32 = 8.0;
259    // _ X 1 1
260    // 1 1 1 _
261    // _ 1 _ _
262    const KERNEL: &[(isize, isize, f32)] = &[
263        //x y  m
264        (1, 0, 1.0),
265        (2, 0, 1.0),
266        //
267        (-1, 1, 1.0),
268        (0, 1, 1.0),
269        (1, 1, 1.0),
270        //
271        (0, 2, 1.0),
272    ];
273}
274
275pub struct Burkes;
276impl private::Sealed for Burkes {}
277impl Dither for Burkes {
278    const DIV: f32 = 32.0;
279    // _ _ X 8 4
280    // 2 4 8 4 2
281    const KERNEL: &[(isize, isize, f32)] = &[
282        //x y  m
283        (1, 0, 8.0),
284        (2, 0, 4.0),
285        //
286        (-2, 1, 2.0),
287        (-1, 1, 4.0),
288        (0, 1, 8.0),
289        (1, 1, 4.0),
290        (2, 1, 2.0),
291    ];
292}
293
294pub struct Bayer;
295impl private::Sealed for Bayer {}
296impl Dither for Bayer {
297    const DIV: f32 = 0.0;
298    const KERNEL: &[(isize, isize, f32)] = &[];
299
300    fn dither_and_palettize(
301        image: &RgbImage,
302        in_palette: &[Lab],
303        target_palette_size: usize,
304    ) -> Vec<usize> {
305        let r = (1usize << 8).div_ceil(target_palette_size.div_ceil(3));
306        let matrix_size = r.next_power_of_two();
307        let r = r as f32;
308        let mut l_matrix = vec![0.0; matrix_size * matrix_size];
309        let mut a_matrix = vec![0.0; matrix_size * matrix_size];
310        let mut b_matrix = vec![0.0; matrix_size * matrix_size];
311
312        (0..matrix_size as u32)
313            .into_par_iter()
314            .zip(l_matrix.par_chunks_mut(matrix_size))
315            .zip(a_matrix.par_chunks_mut(matrix_size))
316            .zip(b_matrix.par_chunks_mut(matrix_size))
317            .for_each(|(((y, row_l), row_a), row_b)| {
318                for (x, ((cell_l, cell_a), cell_b)) in
319                    row_l.iter_mut().zip(row_a).zip(row_b).enumerate()
320                {
321                    let x = x as u32;
322                    let bits = (x ^ y).dilate_expand::<2>().value()
323                        | (y.dilate_expand::<2>().value() << 1);
324                    let bits = bits.reverse_bits() >> bits.leading_zeros();
325                    let partial_thresh = bits as f32 / matrix_size.pow(2) as f32;
326
327                    *cell_l = partial_thresh - 0.5;
328                    *cell_a = partial_thresh - 0.5;
329                    *cell_b = partial_thresh - 0.5;
330                }
331            });
332
333        let mut palette = KdTree::<_, _, 3, 257, u32>::with_capacity(in_palette.len());
334        for (idx, color) in in_palette.iter().enumerate() {
335            palette.add(color.as_ref(), idx);
336        }
337
338        let mut result = vec![0; image.width() as usize * image.height() as usize];
339        let pixels = image.pixels().copied().map(rgb_to_lab).collect::<Vec<_>>();
340
341        pixels
342            .into_par_iter()
343            .enumerate()
344            .zip(&mut result)
345            .for_each(|((idx, p), dest)| {
346                let x = (idx % image.width() as usize) % matrix_size;
347                let y = (idx / image.width() as usize) % matrix_size;
348
349                let m_idx = x + y * matrix_size;
350                let l = p.l + r * l_matrix[m_idx];
351                let a = p.a + r * a_matrix[m_idx];
352                let b = p.b + r * b_matrix[m_idx];
353
354                *dest = palette
355                    .nearest_one::<kiddo::SquaredEuclidean>(&[l, a, b])
356                    .item;
357            });
358
359        result
360    }
361}
362
363/// Uses the Sobol sequence to perturb the colors in a quasi-random manner. This
364/// is somewhere between ordered dithering and stochastic dithering. The results
365/// are generally decent, although they may appear textured like paper.
366pub struct Sobol;
367impl private::Sealed for Sobol {}
368impl Dither for Sobol {
369    const DIV: f32 = 0.0;
370    const KERNEL: &[(isize, isize, f32)] = &[];
371
372    fn dither_and_palettize(
373        image: &RgbImage,
374        in_palette: &[Lab],
375        target_palette_size: usize,
376    ) -> Vec<usize> {
377        let r = (1usize << 8).div_ceil(target_palette_size * target_palette_size.ilog2() as usize)
378            as f32;
379
380        let mut palette = KdTree::<_, _, 3, 257, u32>::with_capacity(in_palette.len());
381        for (idx, color) in in_palette.iter().enumerate() {
382            palette.add(color.as_ref(), idx);
383        }
384
385        let mut result = vec![0; image.width() as usize * image.height() as usize];
386        let pixels = image.pixels().copied().map(rgb_to_lab).collect::<Vec<_>>();
387
388        pixels
389            .into_par_iter()
390            .enumerate()
391            .zip(&mut result)
392            .for_each(|((idx, p), dest)| {
393                let [l, a, b, _] = sample_4d(idx as u32 % (1 << 16), 0, idx as u32 / (1 << 16));
394
395                let l = p.l + (l - 0.5) * 2.0 * r;
396                let a = p.a + (a - 0.5) * 2.0 * r;
397                let b = p.b + (b - 0.5) * 2.0 * r;
398
399                *dest = palette
400                    .nearest_one::<kiddo::SquaredEuclidean>(&[l, a, b])
401                    .item;
402            });
403
404        result
405    }
406}