1use std::cell::RefCell;
6
7use dilate::DilateExpand;
8use image::Rgba;
9use palette::IntoColor;
10use palette::Lab;
11use palette::Srgb;
12use rayon::iter::ParallelIterator;
13
14use crate::PaletteBuilder;
15use crate::dither::PaletteBucketer;
16use crate::private;
17
18pub struct BitPaletteBuilder {
19 pub(crate) shift: usize,
20}
21
22impl BitPaletteBuilder {
23 pub(crate) fn new(palette_size: usize) -> Self {
24 BitPaletteBuilder {
25 shift: Self::shift(palette_size),
26 }
27 }
28
29 pub(crate) fn shift(palette_size: usize) -> usize {
30 24 - palette_size.ilog2() as usize
31 }
32
33 pub(crate) fn index(
34 color: Srgb<u8>,
35 shift: usize,
36 ) -> usize {
37 let r = color.red.dilate_expand::<3>().value();
38 let g = color.green.dilate_expand::<3>().value();
39 let b = color.blue.dilate_expand::<3>().value();
40
41 let rgb = g << 2 | r << 1 | b;
45
46 (rgb >> shift) as usize
47 }
48}
49
50impl private::Sealed for BitPaletteBuilder {}
51
52impl PaletteBuilder for BitPaletteBuilder {
53 const NAME: &'static str = "Bit";
54
55 fn build_palette(
56 image: &image::RgbaImage,
57 palette_size: usize,
58 ) -> Vec<Lab> {
59 let builder = Self::new(palette_size);
60
61 thread_local! {
62 static PALETTE: RefCell<Vec<(u64, u64, u64, u64)>> = RefCell::default();
63 }
64
65 let pool = rayon::ThreadPoolBuilder::new()
66 .num_threads(rayon::current_num_threads())
67 .build()
68 .unwrap();
69
70 pool.install(|| {
71 image.par_pixels().for_each(|pixel| {
72 PALETTE.with_borrow_mut(|palette| {
73 palette.resize(palette_size, (0, 0, 0, 0));
74
75 let pixel = Srgb::<u8>::new(pixel[0], pixel[1], pixel[2]);
76 let index = Self::index(pixel, builder.shift);
77 palette[index].0 += pixel.red as u64;
78 palette[index].1 += pixel.green as u64;
79 palette[index].2 += pixel.blue as u64;
80 palette[index].3 += 1;
81 });
82 });
83 });
84
85 let per_thread_palettes = pool.broadcast(|_ctx| PALETTE.with_borrow_mut(std::mem::take));
86
87 let mut final_palette = vec![(0, 0, 0, 0); palette_size];
88 for palette in per_thread_palettes {
89 for (dest, src) in final_palette.iter_mut().zip(palette) {
90 dest.0 += src.0;
91 dest.1 += src.1;
92 dest.2 += src.2;
93 dest.3 += src.3;
94 }
95 }
96
97 final_palette
98 .into_iter()
99 .filter(|node| node.3 > 0)
100 .map(|node| {
101 let rgb = Srgb::new(
102 (node.0 / node.3) as u8,
103 (node.1 / node.3) as u8,
104 (node.2 / node.3) as u8,
105 );
106 rgb.into_format().into_color()
107 })
108 .collect::<Vec<_>>()
109 }
110
111 fn build_bucketer(
112 palette: &[Lab],
113 palette_size: usize,
114 ) -> impl PaletteBucketer {
115 BitPaletteBucketer::new(palette, palette_size)
116 }
117}
118
119pub struct BitPaletteBucketer {
126 lut: Vec<(usize, usize, f32, f32)>,
127 shift: usize,
128}
129
130impl BitPaletteBucketer {
131 fn new(
136 palette: &[Lab],
137 palette_size: usize,
138 ) -> Self {
139 let shift = BitPaletteBuilder::shift(palette_size);
140 let n_bits = palette_size.ilog2() as usize;
141 let masks = morton_dim_masks(n_bits);
142 let all_mask = palette_size - 1;
143
144 let mut bucket_entries: Vec<Vec<usize>> = vec![vec![]; palette_size];
145 for (pi, &lab) in palette.iter().enumerate() {
146 let rgb: Srgb = lab.into_color();
147 let rgb: Srgb<u8> = rgb.into_format();
148 let idx = BitPaletteBuilder::index(rgb, shift);
149 bucket_entries[idx].push(pi);
150 }
151
152 let lut = (0..palette_size)
153 .map(|bucket_idx| {
154 if bucket_entries[bucket_idx].is_empty() {
155 return (0, 0, 0.0, 0.0);
156 }
157
158 let nearest = bucket_entries[bucket_idx][0];
159 let second = find_second_nearest(
160 nearest,
161 bucket_idx,
162 &bucket_entries,
163 palette,
164 masks,
165 all_mask,
166 );
167 (nearest, second.0, 0.0, second.1)
168 })
169 .collect();
170
171 Self { lut, shift }
172 }
173}
174
175fn morton_dim_masks(n: usize) -> [usize; 3] {
178 let mut masks = [0usize; 3]; for i in 0..n {
180 masks[i % 3] |= 1 << i;
181 }
182 masks
183}
184
185fn morton_inc(
187 z: usize,
188 dim_mask: usize,
189 all_mask: usize,
190) -> Option<usize> {
191 let not_dim = all_mask & !dim_mask;
192 let t = (z | not_dim) + 1;
193 if t > all_mask {
194 return None;
195 }
196 Some((t & dim_mask) | (z & not_dim))
197}
198
199fn morton_dec(
201 z: usize,
202 dim_mask: usize,
203 all_mask: usize,
204) -> Option<usize> {
205 if z & dim_mask == 0 {
206 return None;
207 }
208 let not_dim = all_mask & !dim_mask;
209 let t = (z & dim_mask).wrapping_sub(1);
210 Some((t & dim_mask) | (z & not_dim))
211}
212
213fn morton_neighbor(
216 z: usize,
217 deltas: [i32; 3],
218 masks: &[usize; 3],
219 all_mask: usize,
220) -> Option<usize> {
221 let mut result = z;
222 for (delta, &mask) in deltas.iter().zip(masks.iter()) {
223 match delta.signum() {
224 1 => result = morton_inc(result, mask, all_mask)?,
225 -1 => result = morton_dec(result, mask, all_mask)?,
226 _ => {}
227 }
228 }
229 Some(result)
230}
231
232fn find_second_nearest(
236 nearest: usize,
237 bucket_idx: usize,
238 bucket_entries: &[Vec<usize>],
239 palette: &[Lab],
240 masks: [usize; 3],
241 all_mask: usize,
242) -> (usize, f32) {
243 use palette::color_difference::EuclideanDistance;
244
245 let target = &palette[nearest];
246 let mut best = (0usize, f32::INFINITY);
247
248 for db in -1i32..=1 {
249 for dr in -1i32..=1 {
250 for dg in -1i32..=1 {
251 let nb = if db == 0 && dr == 0 && dg == 0 {
252 Some(bucket_idx)
253 } else {
254 morton_neighbor(bucket_idx, [db, dr, dg], &masks, all_mask)
255 };
256 if let Some(nb) = nb {
257 for &pi in &bucket_entries[nb] {
258 if pi == nearest {
259 continue;
260 }
261 let dist = target.distance_squared(palette[pi]);
262 if dist < best.1 {
263 best = (pi, dist);
264 }
265 }
266 }
267 }
268 }
269 }
270
271 if best.1.is_finite() {
272 return best;
273 }
274
275 for (pi, color) in palette.iter().enumerate() {
276 if pi == nearest {
277 continue;
278 }
279 let dist = target.distance_squared(*color);
280 if dist < best.1 {
281 best = (pi, dist);
282 }
283 }
284
285 best
286}
287
288impl private::Sealed for BitPaletteBucketer {}
289
290impl PaletteBucketer for BitPaletteBucketer {
291 fn nearest(
292 &self,
293 point: &[f32; 3],
294 ) -> usize {
295 let lab = Lab::new(point[0], point[1], point[2]);
296 let rgb: Srgb = lab.into_color();
297 let rgb: Srgb<u8> = rgb.into_format();
298 self.lut[BitPaletteBuilder::index(rgb, self.shift)].0
299 }
300
301 fn nearest_two(
302 &self,
303 point: Rgba<u8>,
304 ) -> [(usize, f32); 2] {
305 let (n1, n2, d1, d2) =
306 self.lut[BitPaletteBuilder::index(Srgb::new(point[0], point[1], point[2]), self.shift)];
307 [(n1, d1), (n2, d2)]
308 }
309
310 fn nearest_rgb(
311 &self,
312 pixel: Rgba<u8>,
313 ) -> usize {
314 let index = BitPaletteBuilder::index(Srgb::new(pixel[0], pixel[1], pixel[2]), self.shift);
315 self.lut[index].0
316 }
317}