1use dilate::DilateExpand;
10use image::RgbImage;
11use kiddo::float::kdtree::KdTree;
12use palette::{
13 color_difference::EuclideanDistance,
14 Lab,
15};
16use rayon::{
17 iter::{
18 IndexedParallelIterator,
19 IntoParallelIterator,
20 ParallelIterator,
21 },
22 slice::ParallelSliceMut,
23};
24use sobol_burley::sample;
25
26use crate::{
27 private,
28 rgb_to_lab,
29};
30
31pub trait Dither: private::Sealed {
34 const KERNEL: &[(isize, isize, f32)];
35 const DIV: f32;
36
37 fn dither_and_palettize(image: &RgbImage, in_palette: &[Lab]) -> Vec<usize> {
40 let pixels = image.pixels().copied().map(rgb_to_lab).collect::<Vec<_>>();
41
42 let mut palette = KdTree::<_, _, 3, 257, u32>::with_capacity(in_palette.len());
43
44 for (idx, color) in in_palette.iter().enumerate() {
45 palette.add(color.as_ref(), idx);
46 }
47
48 let mut result = vec![0; image.width() as usize * image.height() as usize];
49
50 let mut spills = vec![[0.0; 3]; image.width() as usize * image.height() as usize];
51
52 for (idx, p) in pixels.iter().copied().enumerate() {
53 let pixel = <Lab>::new(
54 p.l + spills[idx][0],
55 p.a + spills[idx][1],
56 p.b + spills[idx][2],
57 );
58
59 let palette_idx =
60 palette.nearest_one::<kiddo::SquaredEuclidean>(&[pixel.l, pixel.a, pixel.b]);
61
62 let error0 = pixel.l - in_palette[palette_idx.item].l;
63 let error1 = pixel.a - in_palette[palette_idx.item].a;
64 let error2 = pixel.b - in_palette[palette_idx.item].b;
65 let spill = [error0, error1, error2];
66
67 result[idx] = palette_idx.item;
68
69 for (dx, dy, m) in Self::KERNEL {
70 let x = idx as isize % image.width() as isize + dx;
71 let y = idx as isize / image.width() as isize + dy;
72 if x < 0 || y < 0 || x >= image.width() as isize || y >= image.height() as isize {
73 continue;
74 }
75
76 let jdx = y * image.width() as isize + x;
77 let target = &mut spills[jdx as usize];
78 *target = [
79 target[0] + (spill[0] * m) / Self::DIV / 2.0,
80 target[1] + (spill[1] * m) / Self::DIV / 2.0,
81 target[2] + (spill[2] * m) / Self::DIV / 2.0,
82 ];
83 }
84 }
85
86 result
87 }
88}
89
90pub struct NoDither;
93impl private::Sealed for NoDither {}
94impl Dither for NoDither {
95 const DIV: f32 = 1.0;
96 const KERNEL: &[(isize, isize, f32)] = &[];
97
98 fn dither_and_palettize(image: &RgbImage, in_palette: &[Lab]) -> Vec<usize> {
99 let pixels = image.pixels().copied().map(rgb_to_lab).collect::<Vec<_>>();
100
101 let mut palette = KdTree::<_, _, 3, 32, u32>::with_capacity(in_palette.len());
102
103 for (idx, color) in in_palette.iter().enumerate() {
104 palette.add(color.as_ref(), idx);
105 }
106
107 let mut result = vec![0; image.width() as usize * image.height() as usize];
108 pixels
109 .into_par_iter()
110 .zip(&mut result)
111 .for_each(|(p, dest)| {
112 *dest = palette
113 .nearest_one::<kiddo::SquaredEuclidean>(&[p.l, p.a, p.b])
114 .item;
115 });
116
117 result
118 }
119}
120
121pub struct Sierra;
122impl private::Sealed for Sierra {}
123impl Dither for Sierra {
124 const DIV: f32 = 32.0;
125 const KERNEL: &[(isize, isize, f32)] = &[
129 (1, 0, 5.0),
130 (2, 0, 3.0),
131 (-2, 1, 2.0),
133 (-1, 1, 4.0),
134 (0, 1, 5.0),
135 (1, 1, 4.0),
136 (2, 1, 2.0),
137 (-1, 2, 2.0),
139 (0, 2, 3.0),
140 (1, 2, 2.0),
141 ];
142}
143
144pub struct Sierra2;
145impl private::Sealed for Sierra2 {}
146impl Dither for Sierra2 {
147 const DIV: f32 = 16.0;
148 const KERNEL: &[(isize, isize, f32)] = &[
152 (1, 0, 4.0),
154 (2, 0, 3.0),
155 (-2, 1, 1.0),
157 (-1, 1, 2.0),
158 (0, 1, 3.0),
159 (1, 1, 2.0),
160 (2, 1, 1.0),
161 ];
162}
163
164pub struct SierraLite;
165impl private::Sealed for SierraLite {}
166impl Dither for SierraLite {
167 const DIV: f32 = 4.0;
168 const KERNEL: &[(isize, isize, f32)] = &[
171 (1, 0, 2.0),
173 (-1, 1, 1.0),
175 (0, 1, 1.0),
176 ];
177}
178
179pub struct FloydSteinberg;
180impl private::Sealed for FloydSteinberg {}
181impl Dither for FloydSteinberg {
182 const DIV: f32 = 16.0;
183 const KERNEL: &[(isize, isize, f32)] = &[
186 (1, 0, 7.0),
188 (-1, 1, 3.0),
190 (0, 1, 5.0),
191 (1, 1, 1.0),
192 ];
193}
194
195pub struct JJN;
196impl private::Sealed for JJN {}
197impl Dither for JJN {
198 const DIV: f32 = 48.0;
199 const KERNEL: &[(isize, isize, f32)] = &[
203 (1, 0, 7.0),
205 (2, 0, 5.0),
206 (-2, 1, 3.0),
208 (-1, 1, 5.0),
209 (0, 1, 7.0),
210 (1, 1, 5.0),
211 (2, 1, 3.0),
212 (-2, 2, 1.0),
214 (-1, 2, 2.0),
215 (0, 2, 5.0),
216 (1, 2, 3.0),
217 (2, 2, 1.0),
218 ];
219}
220
221pub struct Stucki;
222impl private::Sealed for Stucki {}
223impl Dither for Stucki {
224 const DIV: f32 = 42.0;
225 const KERNEL: &[(isize, isize, f32)] = &[
229 (1, 0, 8.0),
231 (2, 0, 4.0),
232 (-2, 1, 2.0),
234 (-1, 1, 4.0),
235 (0, 1, 8.0),
236 (1, 1, 4.0),
237 (2, 1, 2.0),
238 (-2, 2, 1.0),
240 (-1, 2, 2.0),
241 (0, 2, 4.0),
242 (1, 2, 2.0),
243 (2, 2, 1.0),
244 ];
245}
246
247pub struct Atkinson;
248impl private::Sealed for Atkinson {}
249impl Dither for Atkinson {
250 const DIV: f32 = 8.0;
251 const KERNEL: &[(isize, isize, f32)] = &[
255 (1, 0, 1.0),
257 (2, 0, 1.0),
258 (-1, 1, 1.0),
260 (0, 1, 1.0),
261 (1, 1, 1.0),
262 (0, 2, 1.0),
264 ];
265}
266
267pub struct Burkes;
268impl private::Sealed for Burkes {}
269impl Dither for Burkes {
270 const DIV: f32 = 32.0;
271 const KERNEL: &[(isize, isize, f32)] = &[
274 (1, 0, 8.0),
276 (2, 0, 4.0),
277 (-2, 1, 2.0),
279 (-1, 1, 4.0),
280 (0, 1, 8.0),
281 (1, 1, 4.0),
282 (2, 1, 2.0),
283 ];
284}
285
286pub struct Bayer;
287impl private::Sealed for Bayer {}
288impl Dither for Bayer {
289 const DIV: f32 = 0.0;
290 const KERNEL: &[(isize, isize, f32)] = &[];
291
292 fn dither_and_palettize(image: &RgbImage, in_palette: &[Lab]) -> Vec<usize> {
293 let matrix_size = image.width().max(image.height()).ilog2().max(2) as usize;
294 let mut matrix = vec![0.0; matrix_size * matrix_size];
295
296 (0..matrix_size as u32)
297 .into_par_iter()
298 .zip(matrix.par_chunks_mut(matrix_size))
299 .for_each(|(y, row)| {
300 for (x, cell) in row.iter_mut().enumerate() {
301 let x = x as u32;
302 let bits = (x ^ y).dilate_expand::<2>().value()
303 | (y.dilate_expand::<2>().value() << 1);
304 let bits = bits.reverse_bits() >> bits.leading_zeros();
305 *cell = bits as f32 / matrix_size.pow(2) as f32;
306 }
307 });
308
309 let mut palette = KdTree::<_, _, 3, 257, u32>::with_capacity(in_palette.len());
310 for (idx, color) in in_palette.iter().enumerate() {
311 palette.add(color.as_ref(), idx);
312 }
313
314 let mut result = vec![0; image.width() as usize * image.height() as usize];
315 let pixels = image.pixels().copied().map(rgb_to_lab).collect::<Vec<_>>();
316
317 pixels
318 .into_par_iter()
319 .enumerate()
320 .zip(&mut result)
321 .for_each(|((idx, p), dest)| {
322 let x = (idx % image.width() as usize) % matrix_size;
323 let y = (idx / image.width() as usize) % matrix_size;
324
325 let [l1, l2] = palette
326 .nearest_n::<kiddo::SquaredEuclidean>(p.as_ref(), 2)
327 .try_into()
328 .unwrap();
329
330 let p_dist = in_palette[l1.item].distance_squared(in_palette[l2.item]);
331 let t = ((l1.distance - l2.distance + p_dist) / (2.0 * p_dist)).clamp(0.0, 1.0);
332
333 let m_idx = x + y * matrix_size;
334 if t > matrix[m_idx] {
335 *dest = l2.item;
336 } else {
337 *dest = l1.item;
338 }
339 });
340
341 result
342 }
343}
344
345pub struct Sobol;
349impl private::Sealed for Sobol {}
350impl Dither for Sobol {
351 const DIV: f32 = 0.0;
352 const KERNEL: &[(isize, isize, f32)] = &[];
353
354 fn dither_and_palettize(image: &RgbImage, in_palette: &[Lab]) -> Vec<usize> {
355 let mut palette = KdTree::<_, _, 3, 257, u32>::with_capacity(in_palette.len());
356 for (idx, color) in in_palette.iter().enumerate() {
357 palette.add(color.as_ref(), idx);
358 }
359
360 let mut result = vec![0; image.width() as usize * image.height() as usize];
361 let pixels = image.pixels().copied().map(rgb_to_lab).collect::<Vec<_>>();
362
363 pixels
364 .into_par_iter()
365 .enumerate()
366 .zip(&mut result)
367 .for_each(|((idx, p), dest)| {
368 let thresh = sample(idx as u32 % (1 << 16), 0, idx as u32 / (1 << 16));
369
370 let [l1, l2] = palette
371 .nearest_n::<kiddo::SquaredEuclidean>(p.as_ref(), 2)
372 .try_into()
373 .unwrap();
374
375 let p_dist = in_palette[l1.item].distance_squared(in_palette[l2.item]);
376 let t = ((l1.distance - l2.distance + p_dist) / (2.0 * p_dist)).clamp(0.0, 1.0);
377
378 if t > thresh {
379 *dest = l2.item;
380 } else {
381 *dest = l1.item;
382 }
383 });
384
385 result
386 }
387}