1#![allow(clippy::identity_op, reason = "symmetry")]
4
5mod diffuse_fns;
6use diffuse_fns::*;
7
8use super::{
9 PixelFormat, SixelError, SixelMean, SixelQuality, SixelResult, SixelSplit, SIXEL_PALETTE_MAX,
10};
11use crate::{vec_ as vec, Dither, HashMap, Ordering, Vec};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15struct BBox {
16 pub ind: i32,
17 pub colors: i32,
18 pub sum: i32,
19}
20impl BBox {
21 const fn sum_cmp(b1: &BBox, b2: &BBox) -> Ordering {
23 if b2.sum > b1.sum {
24 Ordering::Greater
25 } else if b2.sum < b1.sum {
26 Ordering::Less
27 } else {
28 Ordering::Equal
29 }
30 }
31}
32
33#[must_use]
41fn new_color_map(newcolors: i32, depth: i32) -> HashMap<i32, Tuple> {
42 let mut colormap = HashMap::new();
43 for i in 0..newcolors {
44 colormap.insert(i, Tuple { value: 0, tuple: vec![0; depth as usize] });
45 }
46 colormap
47}
48
49#[must_use]
51fn new_box_vector(colors: i32, sum: i32, newcolors: i32) -> Vec<BBox> {
52 let mut result = vec![BBox { ind: 0, colors: 0, sum: 0 }; newcolors as usize];
53
54 result[0].ind = 0;
56 result[0].colors = colors;
57 result[0].sum = sum;
58
59 result
60}
61
62fn find_box_boundaries(
65 colorfreqtable: &mut HashMap<i32, Tuple>,
66 depth: i32,
67 box_start: i32,
68 box_size: i32,
69 minval: &mut [i32],
70 maxval: &mut [i32],
71) {
72 for plane in 0..depth {
73 minval[plane as usize] = colorfreqtable.get(&(box_start)).unwrap().tuple[plane as usize];
74 maxval[plane as usize] = minval[plane as usize];
75 }
76 for i in 1..box_size {
77 for plane in 0..depth {
78 let v = colorfreqtable.get(&(box_start + i)).unwrap().tuple[plane as usize];
79 minval[plane as usize] = minval[plane as usize].min(v);
80 maxval[plane as usize] = maxval[plane as usize].max(v);
81 }
82 }
83}
84
85#[must_use]
87#[expect(unused, reason = "TEMP")]
88fn largest_by_norm(minval: &[i32], maxval: &[i32], depth: i32) -> i32 {
89 let mut largest_spread_so_far = 0;
90 let mut largest_dimension = 0;
91 for plane in 0..depth as usize {
92 let spread = maxval[plane] - minval[plane];
93 if spread > largest_spread_so_far {
94 largest_dimension = plane;
95 largest_spread_so_far = spread;
96 }
97 }
98 largest_dimension as i32
99}
100
101#[must_use]
105#[expect(unused, reason = "TEMP")]
106fn largest_by_luminosity(minval: &[i32], maxval: &[i32], depth: i32) -> i32 {
107 let retval;
108 let lumin_factor = [0.2989, 0.5866, 0.1145];
109
110 if depth == 1 {
111 retval = 0;
112 } else {
113 let mut largest_spread_so_far = 0.0;
115 let mut largest_dimension = 0;
116
117 for plane in 0..3 {
118 let spread = lumin_factor[plane] * (maxval[plane] - minval[plane]) as f32;
119 if spread > largest_spread_so_far {
120 largest_dimension = plane;
121 largest_spread_so_far = spread;
122 }
123 }
124 retval = largest_dimension;
125 }
126 retval as i32
127}
128
129fn center_box(
131 box_start: i32,
132 box_size: i32,
133 colorfreqtable: &mut HashMap<i32, Tuple>,
134 depth: i32,
135 new_tuple: &mut [i32],
136) {
137 for plane in 0..depth {
138 let mut maxval = colorfreqtable.get(&(box_start)).unwrap().tuple[plane as usize];
139 let mut minval = maxval;
140
141 for i in 1..box_size {
142 let v = colorfreqtable.get(&(box_start + i)).unwrap().tuple[plane as usize];
143 minval = minval.min(v);
144 maxval = maxval.max(v);
145 }
146 new_tuple[plane as usize] = (minval + maxval) / 2;
147 }
148}
149
150fn average_colors(
152 box_start: i32,
153 box_size: i32,
154 colorfreqtable: &mut HashMap<i32, Tuple>,
155 depth: i32,
156 new_tuple: &mut [i32],
157) {
158 for plane in 0..depth {
159 let mut sum = 0;
160
161 for i in 0..box_size {
162 sum += colorfreqtable.get(&(box_start + i)).unwrap().tuple[plane as usize];
163 }
164
165 new_tuple[plane as usize] = sum / box_size;
166 }
167}
168
169fn average_pixels(
172 box_start: i32,
173 box_size: i32,
174 colorfreqtable: &mut HashMap<i32, Tuple>,
175 depth: i32,
176 new_tuple: &mut [i32],
177) {
178 let mut n = 0; for i in 0..box_size {
180 n += colorfreqtable.get(&(box_start + i)).unwrap().value;
181 }
182
183 for plane in 0..depth {
184 let mut sum = 0;
185 for i in 0..box_size {
186 sum += colorfreqtable.get(&(box_start + i)).unwrap().tuple[plane as usize]
187 * colorfreqtable.get(&(box_start + i)).unwrap().value;
188 }
189 new_tuple[plane as usize] = sum / n;
190 }
191}
192
193fn color_map_from_bv(
202 newcolors: i32,
203 bv: &[BBox],
204 boxes: i32,
205 colorfreqtable: &mut HashMap<i32, Tuple>,
206 depth: i32,
207 rep: SixelMean,
208) -> HashMap<i32, Tuple> {
209 let mut colormap = new_color_map(newcolors, depth);
210
211 for bi in 0..boxes {
212 match rep {
213 SixelMean::Center => {
214 center_box(
215 bv[bi as usize].ind,
216 bv[bi as usize].colors,
217 colorfreqtable,
218 depth,
219 &mut colormap.get_mut(&bi).unwrap().tuple,
220 );
221 }
222 SixelMean::Colors => {
223 average_colors(
224 bv[bi as usize].ind,
225 bv[bi as usize].colors,
226 colorfreqtable,
227 depth,
228 &mut colormap.get_mut(&bi).unwrap().tuple,
229 );
230 }
231 SixelMean::Auto | SixelMean::Pixels => {
232 average_pixels(
233 bv[bi as usize].ind,
234 bv[bi as usize].colors,
235 colorfreqtable,
236 depth,
237 &mut colormap.get_mut(&bi).unwrap().tuple,
238 );
239 }
240 }
241 }
242 colormap
243}
244
245fn split_box(
253 bv: &mut [BBox],
254 boxes: &mut i32,
255 bi: usize,
256 colorfreqtable: &mut HashMap<i32, Tuple>,
257 depth: i32,
258 _largest: SixelSplit,
259) -> SixelResult<()> {
260 let box_start = bv[bi].ind;
261 let box_size = bv[bi].colors;
262 let sm = bv[bi].sum;
263
264 let max_depth = 16;
265 let mut minval = vec![0; max_depth];
266 let mut maxval = vec![0; max_depth];
267
268 find_box_boundaries(colorfreqtable, depth, box_start, box_size, &mut minval, &mut maxval);
271
272 let mut lowersum = colorfreqtable.get(&box_start).unwrap().value; let mut i = 1;
302 while i < box_size - 1 && lowersum < sm / 2 {
303 lowersum += colorfreqtable.get(&(box_start + i)).unwrap().value;
304 i += 1;
305 }
306 let median_idx = i;
307 bv[bi].colors = median_idx;
310 bv[bi].sum = lowersum;
311 bv[*boxes as usize].ind = box_start + median_idx;
312 bv[*boxes as usize].colors = box_size - median_idx;
313 bv[*boxes as usize].sum = sm - lowersum;
314 (*boxes) += 1;
315
316 bv[0..*boxes as usize].sort_by(BBox::sum_cmp);
317 Ok(())
318}
319
320fn mediancut(
328 colorfreqtable: &mut HashMap<i32, Tuple>,
329 depth: i32,
330 newcolors: i32,
331 largest: SixelSplit,
332 rep: SixelMean,
333 colormap: &mut HashMap<i32, Tuple>,
334) -> SixelResult<()> {
335 let mut sum = 0;
336
337 for i in 0..colorfreqtable.len() {
338 sum += colorfreqtable.get(&(i as i32)).unwrap().value;
339 }
340
341 let mut bv = new_box_vector(colorfreqtable.len() as i32, sum, newcolors);
344 let mut boxes = 1;
345 let mut multi_color_boxes_exist = colorfreqtable.len() > 1;
346
347 while boxes < newcolors && multi_color_boxes_exist {
349 let mut bi = 0;
351 while bi < boxes && bv[bi as usize].colors < 2 {
352 bi += 1;
353 }
354
355 if bi >= boxes {
356 multi_color_boxes_exist = false;
357 } else {
358 split_box(&mut bv, &mut boxes, bi as usize, colorfreqtable, depth, largest)?;
359 }
360 }
361 *colormap = color_map_from_bv(newcolors, &bv, boxes, colorfreqtable, depth, rep);
362
363 Ok(())
364}
365
366fn compute_hash(data: &[u8], i: usize, depth: i32) -> i32 {
368 let mut hash = 0;
369 for n in 0..depth {
370 hash |= (data[i + depth as usize - 1 - n as usize] as i32 >> 3) << (n * 5);
371 }
372 hash
373}
374
375#[derive(Clone)]
377struct Tuple {
378 pub value: i32,
379 pub tuple: Vec<i32>,
380}
381
382fn compute_histogram(
384 data: &[u8],
385 length: i32,
386 depth: i32,
387 quality: SixelQuality,
388) -> SixelResult<HashMap<i32, Tuple>> {
389 let (max_sample, mut step) = match quality {
390 SixelQuality::Low => (18_383, length / depth / 18_383 * depth),
391 SixelQuality::High => (18_383, length / depth / 18_383 * depth),
392 SixelQuality::Auto | SixelQuality::HighColor | SixelQuality::Full => {
393 (4_003_079, length / depth / 4_003_079 * depth)
394 }
395 };
396
397 if length < max_sample * depth {
398 step = 6 * depth;
399 }
400
401 if step <= 0 {
402 step = depth;
403 }
404
405 let mut histogram = vec![0; 1 << (depth * 5)];
406
407 let mut memory = vec![0; 1 << (depth * 5)];
408 let mut it = 0;
409 let mut refe = 0;
410 let _refmap = 0;
411
412 let mut i = 0;
413 while i < length {
414 let bucket_index = compute_hash(data, i as usize, 3) as usize;
415 if histogram[bucket_index] == 0 {
416 memory[refe] = bucket_index;
417 refe += 1;
418 }
419 if histogram[bucket_index] < (1 << (2 * 8)) - 1 {
420 histogram[bucket_index] += 1;
421 }
422
423 i += step;
424 }
425 let mut colorfreqtable = HashMap::new();
426
427 for i in 0..refe {
428 if histogram[memory[i]] > 0 {
429 let mut tuple: Vec<i32> = vec![0; depth as usize];
430 for n in 0..depth {
431 tuple[(depth - 1 - n) as usize] = ((memory[it] >> (n * 5) & 0x1f) << 3) as i32;
432 }
433 colorfreqtable.insert(i as i32, Tuple { value: histogram[memory[i]], tuple });
434 }
435 it += 1;
436 }
437 Ok(colorfreqtable)
438}
439
440#[expect(clippy::too_many_arguments)]
458fn compute_color_map_from_input(
459 data: &[u8],
460 length: i32,
461 depth: i32,
462 req_colors: i32,
463 largest: SixelSplit,
464 rep: SixelMean,
465 quality: SixelQuality,
466 colormap: &mut HashMap<i32, Tuple>,
467 origcolors: &mut i32,
468) -> SixelResult<()> {
469 let mut colorfreqtable = compute_histogram(data, length, depth, quality)?;
470 *origcolors = colorfreqtable.len() as i32;
471
472 if colorfreqtable.len() as i32 <= req_colors {
473 for i in 0..colorfreqtable.len() as i32 {
483 colormap.insert(i, colorfreqtable.get(&i).unwrap().clone());
484 }
485 } else {
486 mediancut(&mut colorfreqtable, depth, req_colors, largest, rep, colormap)?;
487 }
488 Ok(())
489}
490
491#[rustfmt::skip]
493fn diffuse_none(_: &mut [u8], _: i32, _h: i32, _x: i32, _y: i32, _: i32, _: i32) {}
494
495#[must_use]
497const fn mask_a(x: i32, y: i32, c: i32) -> f32 {
498 ((((x + c * 67) + y * 236) * 119) & 255) as f32 / 128.0 - 1.0
499}
500
501#[must_use]
503const fn mask_x(x: i32, y: i32, c: i32) -> f32 {
504 ((((x + c * 29) ^ (y * 149)) * 1234) & 511) as f32 / 256.0 - 1.0
505}
506
507#[must_use]
509fn lookup_normal(
510 pixel: &[u8],
511 depth: i32,
512 palette: &[u8],
513 reqcolor: i32,
514 _cachetable: &mut [u16],
515 complexion: i32,
516) -> i32 {
517 let mut result = -1;
518 let mut diff = i32::MAX;
519
520 for i in 0..reqcolor {
523 let mut distant = 0;
524 let mut r = pixel[0] as i32 - palette[(i * depth + 0) as usize] as i32;
525 distant += r * r * complexion;
526 for n in 1..depth {
527 r = pixel[n as usize] as i32 - palette[(i * depth + n) as usize] as i32;
528 distant += r * r;
529 }
530 if distant < diff {
531 diff = distant;
532 result = i;
533 }
534 }
535
536 result
537}
538
539fn lookup_fast(
541 pixel: &[u8],
542 _depth: i32,
543 palette: &[u8],
544 reqcolor: i32,
545 cachetable: &mut [u16],
546 complexion: i32,
547) -> i32 {
548 let mut result: i32 = -1;
549 let mut diff = i32::MAX;
550 let hash = compute_hash(pixel, 0, 3);
551
552 let cache = cachetable[hash as usize];
553 if cache != 0 {
554 return cache as i32 - 1;
556 }
557 for i in 0..reqcolor {
559 let i = i as usize;
568 let distant = (pixel[0] as i32 - palette[i * 3 + 0] as i32)
569 * (pixel[0] as i32 - palette[i * 3 + 0] as i32)
570 * complexion
571 + (pixel[1] as i32 - palette[i * 3 + 1] as i32)
572 * (pixel[1] as i32 - palette[i * 3 + 1] as i32)
573 + (pixel[2] as i32 - palette[i * 3 + 2] as i32)
574 * (pixel[2] as i32 - palette[i * 3 + 2] as i32);
575 if distant < diff {
577 diff = distant;
578 result = i as i32;
579 }
580 }
581 cachetable[hash as usize] = (result + 1) as u16;
582
583 result
584}
585
586fn lookup_mono_darkbg(
588 pixel: &[u8],
589 depth: i32,
590 _palette: &[u8],
591 reqcolor: i32,
592 _cachetable: &mut [u16],
593 _complexion: i32,
594) -> i32 {
595 let mut distant = 0;
596 for n in 0..depth {
597 distant += pixel[n as usize] as i32;
598 }
599 i32::from(distant >= 128 * reqcolor)
600}
601
602fn lookup_mono_lightbg(
604 pixel: &[u8],
605 depth: i32,
606 _palette: &[u8],
607 reqcolor: i32,
608 _cachetable: &mut [u16],
609 _complexion: i32,
610) -> i32 {
611 let mut distant = 0;
612 for n in 0..depth {
613 distant += pixel[n as usize] as i32;
614 }
615 i32::from(distant < 128 * reqcolor)
616}
617
618#[expect(clippy::too_many_arguments)]
622pub(crate) fn sixel_quant_make_palette(
623 data: &[u8],
624 length: i32,
625 pixelformat: PixelFormat,
626 req_colors: i32,
627 ncolors: &mut i32,
628 origcolors: &mut i32,
629 largest: SixelSplit,
630 rep: SixelMean,
631 quality: SixelQuality,
632) -> SixelResult<Vec<u8>> {
633 let depth = pixelformat.bytes_per_pixel();
634 let mut colormap = HashMap::new();
637 let _ = compute_color_map_from_input(
638 data,
639 length,
640 depth as i32,
641 req_colors,
642 largest,
643 rep,
644 quality,
645 &mut colormap,
646 origcolors,
647 );
648 *ncolors = colormap.len() as i32;
649 let mut result = vec![0; colormap.len() * depth];
650 for i in 0..colormap.len() {
651 for n in 0..depth {
652 result[i * depth + n] = colormap.get(&(i as i32)).unwrap().tuple[n] as u8;
653 }
654 }
655 Ok(result)
656}
657
658#[expect(clippy::too_many_arguments)]
662pub(crate) fn sixel_quant_apply_palette(
663 result: &mut [u8],
664 data: &mut [u8],
665 width: i32,
666 height: i32,
667 depth: i32,
668 palette: &mut Vec<u8>,
669 reqcolor: i32,
670 diffuse: Dither,
671 foptimize: bool,
672 foptimize_palette: bool,
673 complexion: i32,
674 cachetable: Option<&mut [u16]>,
675) -> SixelResult<i32> {
676 let mut ncolors: i32;
677 if reqcolor < 1 {
679 return Err(SixelError::BadArgument);
683 }
684
685 let mut f_mask = false;
686
687 let f_diffuse = if depth != 3 {
688 diffuse_none
689 } else {
690 match diffuse {
691 Dither::Auto | Dither::None => diffuse_none,
692 Dither::Atkinson => diffuse_atkinson,
693 Dither::FS => diffuse_fs,
694 Dither::JaJuNi => diffuse_jajuni,
695 Dither::Stucki => diffuse_stucki,
696 Dither::Burkes => diffuse_burkes,
697 Dither::ADither => {
698 f_mask = true;
699 diffuse_none
700 }
701 Dither::XDither => {
702 f_mask = true;
703 diffuse_none
704 }
705 }
706 };
707 type LookupFunc = fn(&[u8], i32, &[u8], i32, &mut [u16], i32) -> i32;
708 let mut f_lookup: Option<LookupFunc> = None;
709 if reqcolor == 2 {
710 let mut sum1 = 0;
711 let mut sum2 = 0;
712 for n in 0..depth {
713 sum1 += palette[n as usize] as i32;
714 }
715 for n in depth..(depth + depth) {
716 sum2 += palette[n as usize] as i32;
717 }
718 if sum1 == 0 && sum2 == 255 * 3 {
719 f_lookup = Some(lookup_mono_darkbg);
720 } else if sum1 == 255 * 3 && sum2 == 0 {
721 f_lookup = Some(lookup_mono_lightbg);
722 }
723 }
724 if f_lookup.is_none() {
725 if foptimize && depth == 3 {
726 f_lookup = Some(lookup_fast);
727 } else {
728 f_lookup = Some(lookup_normal);
729 }
730 }
731
732 let mut cc = vec![0u16, 1 << (depth * 5)];
733 let indextable = match cachetable {
734 Some(table) => table,
735 None => &mut cc,
736 };
737
738 if foptimize_palette {
739 ncolors = 0;
740 let mut new_palette = vec![0; SIXEL_PALETTE_MAX * depth as usize];
741 let mut migration_map = vec![0; SIXEL_PALETTE_MAX];
742
743 if f_mask {
744 for y in 0..height {
745 for x in 0..width {
746 let mut copy: Vec<u8> = Vec::new();
747
748 let pos = y * width + x;
749 for d in 0..depth {
750 let mut val = data[(pos * depth + d) as usize] as i32;
751 if matches!(diffuse, Dither::ADither) {
752 val += (mask_a(x, y, d) * 32.0) as i32;
753 } else {
754 val += (mask_x(x, y, d) * 32.0) as i32;
755 }
756 copy.push(val.clamp(0, 255) as u8);
757 }
758 let color_index =
760 f_lookup.unwrap()(©, depth, palette, reqcolor, indextable, complexion)
761 as usize;
762 if migration_map[color_index] == 0 {
763 result[pos as usize] = ncolors as u8;
764 for n in 0..depth {
765 new_palette[(ncolors * depth + n) as usize] =
766 palette[color_index * depth as usize + n as usize];
767 }
768 ncolors += 1;
769 migration_map[color_index] = ncolors;
770 } else {
771 result[pos as usize] = migration_map[color_index] as u8 - 1;
772 }
773 }
774 }
775 *palette = new_palette;
776 } else {
777 for y in 0..height {
778 for x in 0..width {
779 let pos = y * width + x;
780 let color_index = f_lookup.unwrap()(
781 &data[(pos * depth) as usize..],
782 depth,
783 palette,
784 reqcolor,
785 indextable,
786 complexion,
787 ) as usize;
788 if migration_map[color_index] == 0 {
789 result[pos as usize] = ncolors as u8;
790 for n in 0..depth {
791 new_palette[(ncolors * depth + n) as usize] =
792 palette[color_index * depth as usize + n as usize];
793 }
794 ncolors += 1;
795 migration_map[color_index] = ncolors;
796 } else {
797 result[pos as usize] = migration_map[color_index] as u8 - 1;
798 }
799 for n in 0..depth {
800 let offset = data[(pos * depth + n) as usize] as i32
801 - palette[color_index * depth as usize + n as usize] as i32;
802 f_diffuse(&mut data[n as usize..], width, height, x, y, depth, offset);
803 }
804 }
805 }
806 *palette = new_palette;
807 }
808 } else {
809 if f_mask {
810 for y in 0..height {
811 for x in 0..width {
812 let mut copy = Vec::new();
813 let pos = y * width + x;
814 for d in 0..depth {
815 let mut val = data[(pos * depth + d) as usize] as i32;
816 if matches!(diffuse, Dither::ADither) {
817 val += (mask_a(x, y, d) * 32.0) as i32;
818 } else {
819 val += (mask_x(x, y, d) * 32.0) as i32;
820 }
821
822 copy.push(val.clamp(0, 255) as u8);
823 }
824 result[pos as usize] = f_lookup.unwrap()(
825 &mut copy, depth, palette, reqcolor, indextable, complexion,
826 ) as u8;
827 }
828 }
829 } else {
830 for y in 0..height {
831 for x in 0..width {
832 let pos = y * width + x;
833 let color_index = f_lookup.unwrap()(
834 &mut data[(pos * depth) as usize..],
835 depth,
836 palette,
837 reqcolor,
838 indextable,
839 complexion,
840 ) as usize;
841 result[pos as usize] = color_index as u8;
842 for n in 0..depth {
843 let offset = data[(pos * depth + n) as usize] as i32
844 - palette[color_index * depth as usize + n as usize] as i32;
845 f_diffuse(&mut data[n as usize..], width, height, x, y, depth, offset);
846 }
847 }
848 }
849 }
850 ncolors = reqcolor;
851 }
852
853 Ok(ncolors)
854}