1use dilate::DilateExpand;
10use image::Rgba;
11use image::RgbaImage;
12use kiddo::float::kdtree::KdTree;
13use palette::Lab;
14use palette::color_difference::EuclideanDistance;
15use rayon::iter::IndexedParallelIterator;
16use rayon::iter::IntoParallelIterator;
17use rayon::iter::IntoParallelRefMutIterator;
18use rayon::iter::ParallelIterator;
19use rayon::slice::ParallelSliceMut;
20use sobol_burley::sample;
21#[cfg(feature = "strum")]
22use strum::Display;
23#[cfg(feature = "strum")]
24use strum::EnumString;
25
26use crate::rgba_to_lab;
27
28pub enum PaletteBucketer {
31 KdTree(KdTreeBucketer),
33 Bit(crate::bit::BitPaletteBucketer),
36}
37
38impl PaletteBucketer {
39 fn nearest(
41 &self,
42 point: &[f32; 3],
43 ) -> usize {
44 match self {
45 Self::KdTree(b) => b.nearest(point),
46 Self::Bit(b) => b.nearest(point),
47 }
48 }
49
50 fn nearest_two(
55 &self,
56 point: Rgba<u8>,
57 ) -> [(usize, f32); 2] {
58 match self {
59 Self::KdTree(b) => b.nearest_two(point),
60 Self::Bit(b) => b.nearest_two(point),
61 }
62 }
63
64 fn nearest_rgb(
66 &self,
67 pixel: Rgba<u8>,
68 ) -> usize {
69 match self {
70 Self::KdTree(b) => b.nearest_rgb(pixel),
71 Self::Bit(b) => b.nearest_rgb(pixel),
72 }
73 }
74}
75
76pub struct KdTreeBucketer(KdTree<f32, usize, 3, 257, u32>);
79
80impl KdTreeBucketer {
81 pub fn new(palette: &[Lab]) -> Self {
83 let mut tree = KdTree::with_capacity(palette.len());
84 for (idx, color) in palette.iter().enumerate() {
85 tree.add(color.as_ref(), idx);
86 }
87 Self(tree)
88 }
89
90 fn nearest(
91 &self,
92 point: &[f32; 3],
93 ) -> usize {
94 self.0.nearest_one::<kiddo::SquaredEuclidean>(point).item
95 }
96
97 fn nearest_two(
98 &self,
99 point: Rgba<u8>,
100 ) -> [(usize, f32); 2] {
101 let point = rgba_to_lab(point);
102 let point = [point.l, point.a, point.b];
103 let [l1, l2] = self
104 .0
105 .nearest_n::<kiddo::SquaredEuclidean>(&point, 2)
106 .try_into()
107 .unwrap();
108 [(l1.item, l1.distance), (l2.item, l2.distance)]
109 }
110
111 fn nearest_rgb(
112 &self,
113 pixel: Rgba<u8>,
114 ) -> usize {
115 let lab = rgba_to_lab(pixel);
116 self.nearest(lab.as_ref())
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127#[cfg_attr(feature = "strum", derive(EnumString, Display))]
128#[cfg_attr(
129 feature = "strum",
130 strum(ascii_case_insensitive, serialize_all = "kebab-case")
131)]
132#[non_exhaustive]
133pub enum Dither {
134 None,
137 Sierra,
139 Sierra2,
141 SierraLite,
143 FloydSteinberg,
145 JJN,
147 Stucki,
149 Atkinson,
151 Burkes,
153 Bayer,
155 Sobol,
160}
161
162impl Dither {
163 pub fn dither_and_palettize(
166 &self,
167 image: &RgbaImage,
168 in_palette: &[Lab],
169 bucketer: &PaletteBucketer,
170 ) -> Vec<usize> {
171 match self {
172 Self::None => no_dither(image, bucketer),
173 Self::Sierra => {
174 error_diffusion_dither(image, in_palette, bucketer, SIERRA_KERNEL, 32.0)
175 }
176 Self::Sierra2 => {
177 error_diffusion_dither(image, in_palette, bucketer, SIERRA2_KERNEL, 16.0)
178 }
179 Self::SierraLite => {
180 error_diffusion_dither(image, in_palette, bucketer, SIERRA_LITE_KERNEL, 4.0)
181 }
182 Self::FloydSteinberg => {
183 error_diffusion_dither(image, in_palette, bucketer, FLOYD_STEINBERG_KERNEL, 16.0)
184 }
185 Self::JJN => error_diffusion_dither(image, in_palette, bucketer, JJN_KERNEL, 48.0),
186 Self::Stucki => {
187 error_diffusion_dither(image, in_palette, bucketer, STUCKI_KERNEL, 42.0)
188 }
189 Self::Atkinson => {
190 error_diffusion_dither(image, in_palette, bucketer, ATKINSON_KERNEL, 8.0)
191 }
192 Self::Burkes => {
193 error_diffusion_dither(image, in_palette, bucketer, BURKES_KERNEL, 32.0)
194 }
195 Self::Bayer => bayer_dither(image, in_palette, bucketer),
196 Self::Sobol => sobol_dither(image, in_palette, bucketer),
197 }
198 }
199}
200
201const SIERRA_KERNEL: &[(isize, isize, f32)] = &[
207 (1, 0, 5.0),
208 (2, 0, 3.0),
209 (-2, 1, 2.0),
211 (-1, 1, 4.0),
212 (0, 1, 5.0),
213 (1, 1, 4.0),
214 (2, 1, 2.0),
215 (-1, 2, 2.0),
217 (0, 2, 3.0),
218 (1, 2, 2.0),
219];
220
221const SIERRA2_KERNEL: &[(isize, isize, f32)] = &[
224 (1, 0, 4.0),
226 (2, 0, 3.0),
227 (-2, 1, 1.0),
229 (-1, 1, 2.0),
230 (0, 1, 3.0),
231 (1, 1, 2.0),
232 (2, 1, 1.0),
233];
234
235const SIERRA_LITE_KERNEL: &[(isize, isize, f32)] = &[
238 (1, 0, 2.0),
240 (-1, 1, 1.0),
242 (0, 1, 1.0),
243];
244
245const FLOYD_STEINBERG_KERNEL: &[(isize, isize, f32)] = &[
248 (1, 0, 7.0),
250 (-1, 1, 3.0),
252 (0, 1, 5.0),
253 (1, 1, 1.0),
254];
255
256const JJN_KERNEL: &[(isize, isize, f32)] = &[
260 (1, 0, 7.0),
262 (2, 0, 5.0),
263 (-2, 1, 3.0),
265 (-1, 1, 5.0),
266 (0, 1, 7.0),
267 (1, 1, 5.0),
268 (2, 1, 3.0),
269 (-2, 2, 1.0),
271 (-1, 2, 2.0),
272 (0, 2, 5.0),
273 (1, 2, 3.0),
274 (2, 2, 1.0),
275];
276
277const STUCKI_KERNEL: &[(isize, isize, f32)] = &[
281 (1, 0, 8.0),
283 (2, 0, 4.0),
284 (-2, 1, 2.0),
286 (-1, 1, 4.0),
287 (0, 1, 8.0),
288 (1, 1, 4.0),
289 (2, 1, 2.0),
290 (-2, 2, 1.0),
292 (-1, 2, 2.0),
293 (0, 2, 4.0),
294 (1, 2, 2.0),
295 (2, 2, 1.0),
296];
297
298const ATKINSON_KERNEL: &[(isize, isize, f32)] = &[
302 (1, 0, 1.0),
304 (2, 0, 1.0),
305 (-1, 1, 1.0),
307 (0, 1, 1.0),
308 (1, 1, 1.0),
309 (0, 2, 1.0),
311];
312
313const BURKES_KERNEL: &[(isize, isize, f32)] = &[
316 (1, 0, 8.0),
318 (2, 0, 4.0),
319 (-2, 1, 2.0),
321 (-1, 1, 4.0),
322 (0, 1, 8.0),
323 (1, 1, 4.0),
324 (2, 1, 2.0),
325];
326
327fn no_dither(
330 image: &RgbaImage,
331 bucketer: &PaletteBucketer,
332) -> Vec<usize> {
333 let mut result = vec![0; image.width() as usize * image.height() as usize];
334 image.par_pixels().zip(&mut result).for_each(|(p, dest)| {
335 *dest = bucketer.nearest_rgb(*p);
336 });
337
338 result
339}
340
341fn error_diffusion_dither(
342 image: &RgbaImage,
343 in_palette: &[Lab],
344 bucketer: &PaletteBucketer,
345 kernel: &[(isize, isize, f32)],
346 div: f32,
347) -> Vec<usize> {
348 let pixels = image.pixels().copied().map(rgba_to_lab).collect::<Vec<_>>();
349
350 let mut result = vec![0; image.width() as usize * image.height() as usize];
351
352 let mut spills = vec![[0.0; 3]; image.width() as usize * image.height() as usize];
353
354 for (idx, p) in pixels.iter().copied().enumerate() {
355 let pixel = <Lab>::new(
356 p.l + spills[idx][0],
357 p.a + spills[idx][1],
358 p.b + spills[idx][2],
359 );
360
361 let palette_idx = bucketer.nearest(&[pixel.l, pixel.a, pixel.b]);
362
363 let error0 = pixel.l - in_palette[palette_idx].l;
364 let error1 = pixel.a - in_palette[palette_idx].a;
365 let error2 = pixel.b - in_palette[palette_idx].b;
366 let spill = [error0, error1, error2];
367
368 result[idx] = palette_idx;
369
370 for (dx, dy, m) in kernel {
371 let x = idx as isize % image.width() as isize + dx;
372 let y = idx as isize / image.width() as isize + dy;
373 if x < 0 || y < 0 || x >= image.width() as isize || y >= image.height() as isize {
374 continue;
375 }
376
377 let jdx = y * image.width() as isize + x;
378 let target = &mut spills[jdx as usize];
379 *target = [
380 target[0] + (spill[0] * m) / div / 2.0,
381 target[1] + (spill[1] * m) / div / 2.0,
382 target[2] + (spill[2] * m) / div / 2.0,
383 ];
384 }
385 }
386
387 result
388}
389
390fn bayer_dither(
391 image: &RgbaImage,
392 in_palette: &[Lab],
393 bucketer: &PaletteBucketer,
394) -> Vec<usize> {
395 let order = image.width().min(image.height()).ilog2().max(2) as usize;
396 let matrix_size = 1 << order;
397 let total_bits = 2 * order;
398 let mut matrix = vec![0.0; matrix_size * matrix_size];
399
400 (0..matrix_size as u32)
401 .into_par_iter()
402 .zip(matrix.par_chunks_mut(matrix_size))
403 .for_each(|(y, row)| {
404 for (x, cell) in row.iter_mut().enumerate() {
405 let x = x as u32;
406 let bits =
407 (x ^ y).dilate_expand::<2>().value() | (y.dilate_expand::<2>().value() << 1);
408 let bits = bits.reverse_bits() >> (u32::BITS - total_bits as u32);
409 *cell = bits as f32 / matrix_size.pow(2) as f32;
410 }
411 });
412
413 let max_matrix = matrix
414 .iter()
415 .copied()
416 .max_by(|l, r| l.total_cmp(r))
417 .unwrap_or(1.0);
418 let min_matrix = matrix
419 .iter()
420 .copied()
421 .min_by(|l, r| l.total_cmp(r))
422 .unwrap_or(0.0);
423 matrix.par_iter_mut().for_each(|cell| {
424 *cell = (*cell - min_matrix) / (max_matrix - min_matrix);
425 });
426
427 let mut result = vec![0; image.width() as usize * image.height() as usize];
428 image
429 .par_pixels()
430 .enumerate()
431 .zip(&mut result)
432 .for_each(|((idx, p), dest)| {
433 let x = (idx % image.width() as usize) % matrix_size;
434 let y = (idx / image.width() as usize) % matrix_size;
435
436 let [(l1_item, l1_dist), (l2_item, l2_dist)] = bucketer.nearest_two(*p);
437
438 let p_dist = in_palette[l1_item].distance_squared(in_palette[l2_item]);
439 let t = ((l1_dist - l2_dist + p_dist) / (2.0 * p_dist)).clamp(0.0, 1.0);
440
441 let m_idx = x + y * matrix_size;
442 if t > matrix[m_idx] {
443 *dest = l2_item;
444 } else {
445 *dest = l1_item;
446 }
447 });
448
449 result
450}
451
452fn sobol_dither(
453 image: &RgbaImage,
454 in_palette: &[Lab],
455 bucketer: &PaletteBucketer,
456) -> Vec<usize> {
457 let mut result = vec![0; image.width() as usize * image.height() as usize];
458 image
459 .par_pixels()
460 .enumerate()
461 .zip(&mut result)
462 .for_each(|((idx, p), dest)| {
463 let thresh = sample(idx as u32 % (1 << 16), 0, idx as u32 / (1 << 16));
464
465 let [(l1_item, l1_dist), (l2_item, l2_dist)] = bucketer.nearest_two(*p);
466
467 let p_dist = in_palette[l1_item].distance_squared(in_palette[l2_item]);
468 let t = ((l1_dist - l2_dist + p_dist) / (2.0 * p_dist)).clamp(0.0, 1.0);
469
470 if t > thresh {
471 *dest = l2_item;
472 } else {
473 *dest = l1_item;
474 }
475 });
476
477 result
478}