1use alloc::vec::Vec;
4
5use crate::std::{
6 cmp,
7 sync::atomic::{AtomicUsize, Ordering},
8};
9
10use crate::{Error, Result};
11
12mod box_vector;
13mod palette;
14mod tuple;
15
16pub use box_vector::*;
17pub use palette::*;
18pub use tuple::*;
19
20static COMPARE_PLANE: AtomicUsize = AtomicUsize::new(0);
21
22pub fn global_compare_plane() -> usize {
28 COMPARE_PLANE.load(Ordering::Relaxed)
29}
30
31pub fn set_global_compare_plane(plane: usize) {
37 COMPARE_PLANE.store(plane, Ordering::SeqCst)
38}
39
40pub fn compare_plane(lhs: &TupleInt, rhs: &TupleInt) -> cmp::Ordering {
42 let plane = global_compare_plane();
43
44 let lhs_tuple = lhs.tuple.get(plane).unwrap_or(&0);
45 let rhs_tuple = rhs.tuple.get(plane).unwrap_or(&0);
46
47 lhs_tuple.cmp(rhs_tuple)
48}
49
50pub type Histogram = Vec<u16>;
52
53#[repr(u8)]
55#[derive(Clone, Copy, Debug, Default, PartialEq)]
56pub enum BuiltinDither {
57 #[default]
59 MonoDark = 0,
60 MonoLight,
62 Xterm16,
64 Xterm256,
66 Vt340Mono,
68 Vt340Color,
70}
71
72#[derive(Clone, Debug, PartialEq)]
74#[repr(C)]
75pub struct ColorMap {
76 pub table: Vec<TupleInt>,
77}
78
79impl ColorMap {
80 pub const fn new() -> Self {
82 Self { table: Vec::new() }
83 }
84
85 pub fn with_capacity(cap: usize) -> Self {
87 Self {
88 table: Vec::with_capacity(cap),
89 }
90 }
91
92 pub fn with_colors_and_depth(colors: usize, depth: usize) -> Self {
94 Self {
95 table: vec![TupleInt::with_depth(depth); colors],
96 }
97 }
98
99 pub fn table(&self) -> &[TupleInt] {
101 self.table.as_ref()
102 }
103
104 pub fn len(&self) -> usize {
106 self.table.len()
107 }
108
109 pub fn is_empty(&self) -> bool {
111 self.table.is_empty()
112 }
113
114 pub fn compute_from_input(
132 data: &[u8],
133 depth: usize,
134 req_colors: usize,
135 method_for_largest: MethodForLargest,
136 method_for_rep: MethodForRep,
137 quality_mode: QualityMode,
138 orig_colors: &mut usize,
139 ) -> Result<Self> {
140 let mut color_map = Self::compute_histogram(data, depth, quality_mode)?;
141 *orig_colors = color_map.table.len();
142
143 if color_map.table.len() <= req_colors {
144 Ok(color_map)
145 } else {
146 color_map.median_cut(depth, req_colors, method_for_largest, method_for_rep)
147 }
148 }
149
150 pub fn compute_histogram(data: &[u8], depth: usize, quality_mode: QualityMode) -> Result<Self> {
152 let len = data.len();
153
154 let max_sample: usize = match quality_mode {
155 QualityMode::Low | QualityMode::High => 18383,
156 _ => 4_003_079,
157 };
158
159 let mut step = if len < max_sample.saturating_mul(depth) {
160 depth.saturating_mul(6)
161 } else {
162 len.saturating_div(depth)
163 .saturating_div(max_sample)
164 .saturating_mul(depth)
165 };
166
167 if step == 0 {
168 step = depth;
169 }
170
171 let hist_cap = 1usize.wrapping_shl(depth.saturating_mul(5) as u32);
172 let mut histogram = vec![0u16; hist_cap];
173 let mut hist_ref: Vec<u16> = Vec::with_capacity(hist_cap);
174
175 for i in (0..len).step_by(step) {
176 let bucket_idx = compute_hash(data[i..].as_ref(), 3);
177 if bucket_idx >= hist_cap {
178 continue;
179 }
180
181 let hist_val = histogram[bucket_idx];
182 if hist_val == 0 {
183 hist_ref.push(bucket_idx as u16);
184 }
185
186 if hist_val < u16::MAX {
187 histogram[bucket_idx] = hist_val.saturating_add(1);
188 }
189 }
190
191 let map_len = hist_ref.len();
192 let mut color_map = Self::with_capacity(map_len);
193
194 for &i in hist_ref.iter() {
195 let hist_val = histogram[i as usize];
196 if hist_val > 0 {
197 let mut tuple = TupleInt::new();
198 tuple.set_value(hist_val as u32);
199 tuple.tuple = (0..map_len)
200 .map(|n| ((i >> (n * 5) & 0x1f) << 3) as u32)
201 .rev()
202 .collect();
203
204 color_map.table.push(tuple);
205 }
206 }
207
208 Ok(color_map)
209 }
210
211 pub fn median_cut(
219 &mut self,
220 depth: usize,
221 new_colors: usize,
222 method_for_largest: MethodForLargest,
223 method_for_rep: MethodForRep,
224 ) -> Result<Self> {
225 let sum = self.table.iter().map(|t| t.value as usize).sum();
226
227 let mut bv = BoxVector::create(self.table.len(), new_colors, sum);
230
231 let mut boxes = 1usize;
232 let mut multicolor_boxes_exist = self.table.len() > 1;
233
234 while boxes < new_colors && multicolor_boxes_exist {
236 let bi = bv.0.iter().filter(|b| b.colors >= 2).count();
237
238 if bi == 0 {
239 multicolor_boxes_exist = false;
240 } else {
241 boxes = self.split_box(&mut bv, boxes, bi, depth, method_for_largest)?;
242 }
243 }
244
245 self.from_bv(&bv, boxes, depth, new_colors, method_for_rep)
246 }
247
248 pub fn split_box(
256 &mut self,
257 bv: &mut BoxVector,
258 mut boxes: usize,
259 bi: usize,
260 depth: usize,
261 method_for_largest: MethodForLargest,
262 ) -> Result<usize> {
263 const MAX_DEPTH: usize = 16;
264
265 if depth > MAX_DEPTH {
266 return Err(Error::Quant(format!(
267 "invalid depth, max: {MAX_DEPTH}, have: {depth}"
268 )));
269 }
270
271 let bv_len = bv.0.len();
272 if bi >= bv_len || boxes >= bv_len {
273 return Err(Error::Quant(format!("invalid BoxVector index and/or number, length: {bv_len}, index: {bi}, boxes: {boxes}")));
274 }
275
276 let box_start = bv[bi].ind;
277 let box_size = bv[bi].colors;
278 let sm = bv[bi].sum;
279
280 let mut min_val = [0u32; MAX_DEPTH];
281 let mut max_val = [0u32; MAX_DEPTH];
282
283 self.find_box_boundaries(depth, box_start, min_val.as_mut(), max_val.as_mut())?;
284
285 let largest_dimension = match method_for_largest {
296 MethodForLargest::Norm => largest_by_norm(&min_val[..depth], &max_val[..depth]),
297 MethodForLargest::Lum => largest_by_luminosity(&min_val[..depth], &max_val[..depth]),
298 _ => {
299 return Err(Error::Quant(format!(
300 "internal: invalid valud for MethodForLargest, have: {method_for_largest}"
301 )));
302 }
303 };
304
305 let mut lower_sum = self.table[box_start].value as usize;
307
308 let median_index = {
311 let mut i = 1usize;
315
316 while i < box_size && lower_sum < sm / 2 {
317 lower_sum = lower_sum.saturating_add(self.table[box_start + i].value as usize);
318 i += 1;
319 }
320
321 i
322 };
323
324 bv[bi].colors = median_index;
327 bv[bi].sum = lower_sum;
328 bv[boxes].ind = box_start.saturating_add(median_index);
329 bv[boxes].colors = box_size.saturating_sub(median_index);
330 bv[boxes].sum = sm.saturating_sub(lower_sum);
331
332 boxes = boxes.saturating_add(1);
333
334 set_global_compare_plane(largest_dimension);
335
336 self.table[box_start..box_size]
337 .as_mut()
338 .sort_by(compare_plane);
339 bv.0[..boxes].as_mut().sort_by(sum_compare);
340
341 Ok(boxes)
342 }
343
344 pub fn from_bv(
358 &self,
359 bv: &BoxVector,
360 boxes: usize,
361 depth: usize,
362 new_colors: usize,
363 method_for_rep: MethodForRep,
364 ) -> Result<Self> {
365 let mut colormap = ColorMap::with_colors_and_depth(new_colors, depth);
366 let bv_ref = bv.as_ref();
367
368 for (bi, table) in bv_ref[..boxes]
369 .iter()
370 .zip(colormap.table[..boxes].iter_mut())
371 {
372 table.tuple = match method_for_rep {
373 MethodForRep::CenterBox => self.center_box(bi.ind, bi.colors, depth)?,
374 MethodForRep::AverageColors => self.average_colors(bi.ind, bi.colors, depth)?,
375 MethodForRep::AveragePixels => self.average_pixels(bi.ind, bi.colors, depth)?,
376 _ => {
377 return Err(Error::Quant(format!(
378 "invalid value of method_for_rep: {method_for_rep}"
379 )))
380 }
381 };
382 }
383
384 Ok(colormap)
385 }
386
387 pub fn find_box_boundaries(
396 &self,
397 depth: usize,
398 box_start: usize,
399 min_val: &mut [Sample],
400 max_val: &mut [Sample],
401 ) -> Result<()> {
402 let table_len = self.table.len();
403 let min_val_len = min_val.len();
404 let max_val_len = max_val.len();
405 let tuple_len = if let Some(tuple) = self.table.get(box_start) {
406 tuple.tuple.len()
407 } else {
408 0
409 };
410 if (box_start + 1) > table_len
411 || depth > tuple_len
412 || depth > min_val_len
413 || depth > max_val_len
414 {
415 return Err(Error::Quant(
416 format!("box_start and/or depth is out-of-bounds, table length: {table_len}, box_start: {box_start}, tuple length: {tuple_len}, min_val length: {min_val_len} max_val length: {max_val_len}")
417 ));
418 }
419
420 for (plane, &val) in self.table[box_start].tuple[..depth].iter().enumerate() {
421 min_val[plane] = val;
422 max_val[plane] = val;
423 }
424
425 for tuple in self.table[box_start + 1..].iter() {
426 for (plane, &val) in tuple.tuple[..depth].iter().enumerate() {
427 if val < min_val[plane] {
428 min_val[plane] = val;
429 }
430
431 if val > max_val[plane] {
432 max_val[plane] = val;
433 }
434 }
435 }
436
437 Ok(())
438 }
439
440 pub fn center_box(&self, box_start: usize, box_size: usize, depth: usize) -> Result<Tuple> {
442 let table_len = self.table.len();
443 let tuple_len = if let Some(tuple) = self.table.get(box_start) {
444 tuple.tuple.len()
445 } else {
446 0
447 };
448
449 if box_start > table_len || box_size > table_len || depth > tuple_len {
450 Err(Error::Quant(format!("box_start, box_size, and/or depth is out-of-bounds: table length: {table_len}, box_start: {box_start}, box_size {box_size}, tuple length: {tuple_len}, depth: {depth}")))
451 } else {
452 let mut new_tuple = Tuple::with_capacity(depth);
453
454 for plane in 0..depth {
455 let val = self.table[box_start].tuple[plane];
456 let (mut min_val, mut max_val) = (val, val);
457
458 for tuple in self.table[box_start + 1..box_size].iter() {
459 let v = tuple.tuple[plane];
460 min_val = cmp::min(v, min_val);
461 max_val = cmp::max(v, max_val);
462 }
463
464 new_tuple.push(min_val.saturating_add(max_val).saturating_div(2));
465 }
466
467 Ok(new_tuple)
468 }
469 }
470
471 pub fn average_colors(&self, box_start: usize, box_size: usize, depth: usize) -> Result<Tuple> {
473 let table_len = self.table.len();
474 if box_start > table_len || box_size > table_len || box_start > box_size {
475 Err(Error::Quant(format!("box_start and/or box_size is out-of-bounds, table length: {table_len}, box start: {box_start}, box size: {box_size}")))
476 } else {
477 let mut new_tuple = Tuple::with_capacity(depth);
478
479 (0..depth).for_each(|plane| {
480 let sum: u32 = self.table[box_start..box_size]
481 .iter()
482 .map(|t| t.tuple.get(plane).unwrap_or(&0))
483 .sum();
484
485 new_tuple.push(sum / box_size as u32);
486 });
487
488 Ok(new_tuple)
489 }
490 }
491
492 pub fn average_pixels(&self, box_start: usize, box_size: usize, depth: usize) -> Result<Tuple> {
494 let table_len = self.table.len();
495 if box_start > table_len || box_size > table_len || box_start > box_size {
496 Err(Error::Quant(format!("box_start and/or box_size is out-of-bounds, table length: {table_len}, box start: {box_start}, box size: {box_size}")))
497 } else {
498 let n: u32 = self.table[box_start..box_size]
499 .iter()
500 .map(|t| t.value)
501 .sum();
502
503 let mut new_tuple = Tuple::with_capacity(depth);
504
505 (0..depth).for_each(|plane| {
506 let sum: u32 = self.table[box_start..box_size]
507 .iter()
508 .map(|t| t.tuple.get(plane).unwrap_or(&0).saturating_mul(t.value))
509 .sum();
510
511 new_tuple.push(sum / n);
512 });
513
514 Ok(new_tuple)
515 }
516 }
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522
523 #[test]
524 fn test_1() -> Result<()> {
525 let min_val = [1u32];
526 let max_val = [2u32];
527
528 assert_eq!(largest_by_luminosity(min_val.as_ref(), max_val.as_ref()), 0);
529
530 let min_val = [1u32, 2u32, 3u32];
532 let max_val = [2u32, 4u32, 8u32];
533
534 assert_eq!(largest_by_luminosity(min_val.as_ref(), max_val.as_ref()), 1);
535
536 let min_val = [1u32, 3u32, 2u32];
537 let max_val = [2u32, 8u32, 4u32];
538
539 assert_eq!(largest_by_luminosity(min_val.as_ref(), max_val.as_ref()), 1);
540
541 let min_val = [1u32, 2u32, 3u32];
544 let max_val = [2u32, 4u32, 20u32];
545
546 assert_eq!(largest_by_luminosity(min_val.as_ref(), max_val.as_ref()), 2);
547
548 let min_val = [3u32, 1u32, 2u32];
549 let max_val = [8u32, 2u32, 4u32];
550
551 assert_eq!(largest_by_luminosity(min_val.as_ref(), max_val.as_ref()), 0);
552
553 let min_val = [3u32, 1u32, 2u32, 0u32];
555 let max_val = [8u32, 2u32, 4u32, 0u32];
556
557 assert_eq!(largest_by_luminosity(min_val.as_ref(), max_val.as_ref()), 0);
558
559 let min_val = [3u32, 1u32];
560 let max_val = [8u32, 2u32];
561 assert_eq!(largest_by_luminosity(min_val.as_ref(), max_val.as_ref()), 0);
562
563 Ok(())
564 }
565}