libsixel_rs/quant/
palette.rs

1use alloc::vec::Vec;
2
3use crate::{
4    dither::SixelDither,
5    pixel_format::PixelFormat,
6    std::{cmp, fmt},
7    Error, Result,
8};
9
10use super::{ColorMap, Sample};
11
12/// Maximum number of palette items.
13pub const PALETTE_MAX: usize = 256;
14
15/// Matching enum for comparing a selected lookup function.
16#[repr(u32)]
17#[derive(Clone, Copy, Debug, Default, PartialEq)]
18pub enum LookupFunction {
19    #[default]
20    Normal,
21    Fast,
22    MonoDarkBg,
23    MonoLightBg,
24}
25
26impl LookupFunction {
27    /// Creates a new [LookupFunction].
28    pub const fn new() -> Self {
29        Self::Normal
30    }
31}
32
33/// Method for finding the largest dimension for splitting, and sorting by that component.
34#[repr(u8)]
35#[derive(Clone, Copy, Debug, Default, PartialEq)]
36pub enum MethodForLargest {
37    /// Choose automatically the method for finding the largest dimension
38    #[default]
39    Auto = 0,
40    /// Simply comparing the range in RGB space
41    Norm,
42    /// Transforming into luminosities before the comparison
43    Lum,
44}
45
46impl MethodForLargest {
47    /// Creates a new [MethodForLargest].
48    pub const fn new() -> Self {
49        Self::Auto
50    }
51}
52
53impl From<MethodForLargest> for &'static str {
54    fn from(val: MethodForLargest) -> Self {
55        match val {
56            MethodForLargest::Auto => "auto",
57            MethodForLargest::Norm => "normalized",
58            MethodForLargest::Lum => "luminosity",
59        }
60    }
61}
62
63impl From<&MethodForLargest> for &'static str {
64    fn from(val: &MethodForLargest) -> Self {
65        (*val).into()
66    }
67}
68
69impl fmt::Display for MethodForLargest {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        write!(f, "{}", <&str>::from(self))
72    }
73}
74
75/// Method for choosing a color from the box
76#[repr(u8)]
77#[derive(Clone, Copy, Debug, Default, PartialEq)]
78pub enum MethodForRep {
79    /// Choose automatically the method for selecting representative color from each box
80    #[default]
81    Auto = 0,
82    /// Choose the center of the box
83    CenterBox,
84    /// Choose the average all the color in the box (specified in Heckbert's paper)
85    AverageColors,
86    /// Choose the average all the pixels in the box
87    AveragePixels,
88}
89
90impl MethodForRep {
91    /// Creates a new [MethodForRep].
92    pub const fn new() -> Self {
93        Self::Auto
94    }
95}
96
97impl From<MethodForRep> for &'static str {
98    fn from(val: MethodForRep) -> Self {
99        match val {
100            MethodForRep::Auto => "auto",
101            MethodForRep::CenterBox => "center box",
102            MethodForRep::AverageColors => "average colors",
103            MethodForRep::AveragePixels => "average pixels",
104        }
105    }
106}
107
108impl From<&MethodForRep> for &'static str {
109    fn from(val: &MethodForRep) -> Self {
110        (*val).into()
111    }
112}
113
114impl fmt::Display for MethodForRep {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(f, "{}", <&str>::from(self))
117    }
118}
119
120/// Method for diffusing
121#[repr(u8)]
122#[derive(Clone, Copy, Debug, Default, PartialEq)]
123pub enum MethodForDiffuse {
124    /// Choose diffusion type automatically
125    #[default]
126    Auto = 0,
127    /// Don't diffuse
128    None,
129    /// Diffuse with Bill Atkinson's method
130    Atkinson,
131    /// Diffuse with Floyd-Steinberg method
132    Fs,
133    /// Diffuse with Jarvis, Judice & Ninke method
134    Jajuni,
135    /// Diffuse with Stucki's method
136    Stucki,
137    /// Diffuse with Burkes' method
138    Burkes,
139    /// Positionally stable arithmetic dither
140    ADither,
141    /// Positionally stable arithmetic XOR-based dither
142    XDither,
143}
144
145impl MethodForDiffuse {
146    /// Creates a new [MethodForDiffuse].
147    pub const fn new() -> Self {
148        Self::Auto
149    }
150
151    /// Gets whether the [MethodForDiffuse] applies a mask.
152    pub const fn is_mask(&self) -> bool {
153        matches!(self, Self::ADither | Self::XDither)
154    }
155}
156
157/// Method used for resampling
158#[repr(u8)]
159#[derive(Clone, Copy, Debug, Default, PartialEq)]
160pub enum MethodForResampling {
161    /// Use nearest neighbor method
162    Nearest = 0,
163    /// Use Guaussian filter
164    Gaussian,
165    /// Use Hanning filter
166    Hanning,
167    /// Use Hamming filter
168    Hamming,
169    /// Use bilinear filter
170    #[default]
171    Bilinear,
172    /// Use Welsh filter
173    Welsh,
174    /// Use bicubic filter
175    Bicubic,
176    /// Use Lanczos-2 filter
177    Lanczos2,
178    /// Use Lanczos-3 filter
179    Lanczos3,
180    /// Use Lanczos-4 filter
181    Lanczos4,
182}
183
184impl MethodForResampling {
185    /// Creates a new [MethodForResampling].
186    pub const fn new() -> Self {
187        Self::Bilinear
188    }
189}
190
191/// Quality modes
192#[repr(u8)]
193#[derive(Clone, Copy, Debug, Default, PartialEq)]
194pub enum QualityMode {
195    /// Choose quality mode automatically
196    #[default]
197    Auto = 0,
198    /// High quality palette construction
199    High,
200    /// Low quality palette construction
201    Low,
202    /// Full quality palette construction
203    Full,
204    /// High color palette construction
205    HighColor,
206}
207
208impl QualityMode {
209    /// Creates a new [QualityMode].
210    pub const fn new() -> Self {
211        Self::Auto
212    }
213}
214
215/// Groups palette configuration settings together.
216pub struct PaletteConfig {
217    pub pixel_format: PixelFormat,
218    pub method_for_largest: MethodForLargest,
219    pub method_for_rep: MethodForRep,
220    pub quality_mode: QualityMode,
221}
222
223impl PaletteConfig {
224    /// Creates a new [PaletteConfig].
225    pub const fn new() -> Self {
226        Self {
227            pixel_format: PixelFormat::new(),
228            method_for_largest: MethodForLargest::new(),
229            method_for_rep: MethodForRep::new(),
230            quality_mode: QualityMode::new(),
231        }
232    }
233}
234
235/// Compute a hash for a [Histogram](super::Histogram).
236pub fn compute_hash(data: &[u8], depth: usize) -> usize {
237    let depth = cmp::min(depth, data.len());
238    let mut hash = 0;
239
240    for (n, b) in data[..depth].iter().enumerate().rev() {
241        hash |= (b.wrapping_shr(3) as usize).wrapping_shl((depth - n).saturating_mul(5) as u32);
242    }
243
244    hash
245}
246
247/// Gets the largest value by its normalized difference.
248pub fn largest_by_norm(min_val: &[Sample], max_val: &[Sample]) -> usize {
249    let depth = min_val.len();
250
251    let mut largest_spread = 0;
252    let mut largest_dimension = 0;
253
254    for (plane, (&minv, &maxv)) in min_val[..depth]
255        .iter()
256        .zip(max_val[..depth].iter())
257        .enumerate()
258    {
259        let delta = maxv.saturating_sub(minv);
260        if delta > largest_spread {
261            largest_spread = delta;
262            largest_dimension = plane;
263        }
264    }
265
266    largest_dimension
267}
268
269/// Gets the largest value by its luminosity.
270///
271/// From the `libsixel` docs:
272///
273/// ```no_build, no_run
274/// This subroutine presumes that the tuple type is either
275/// BLACKANDWHITE, GRAYSCALE, or RGB (which implies pamP->depth is 1 or 3).
276/// To save time, we don't actually check it.
277/// ```
278///
279/// `To save time, we don't actually check it.` <- except we do, because bounds are a thing.
280pub fn largest_by_luminosity(min_val: &[Sample], max_val: &[Sample]) -> usize {
281    let depth = min_val.len();
282    let min_val_len = min_val.len();
283    let max_val_len = max_val.len();
284
285    if min_val_len != 3 || max_val_len != 3 || depth == 1 || min_val_len != max_val_len {
286        0
287    } else {
288        let lumin_factor = [0.2989f32, 0.5866f32, 0.1145f32];
289        let (mut max_lumin, mut max_dimension): (f32, usize) = (0.0, 0);
290
291        for (plane, (&minv, &maxv)) in min_val[..3].iter().zip(max_val[..3].iter()).enumerate() {
292            let lumin = lumin_factor[plane] * maxv.saturating_sub(minv) as f32;
293            if lumin > max_lumin {
294                max_lumin = lumin;
295                max_dimension = plane;
296            }
297        }
298
299        max_dimension
300    }
301}
302
303/// Diffuses error energy to surrounding pixels.
304pub fn error_diffuse(
305    data: &mut [u8],
306    pos: i32,
307    depth: i32,
308    error: i32,
309    numerator: i32,
310    denominator: i32,
311    area: i32,
312) -> Result<()> {
313    if pos < 0 || pos >= area {
314        Err(Error::Quant(format!(
315            "invalid position: {pos}, area: {area}"
316        )))
317    } else if denominator == 0 {
318        Err(Error::Quant("invalid denominator, divide-by-zero".into()))
319    } else if let Some(ret) = data.get_mut((pos * depth) as usize) {
320        let mut c = (*ret as i32)
321            .saturating_add(error.saturating_mul(numerator).saturating_div(denominator));
322
323        if c < 0 {
324            c = 0;
325        }
326
327        if c >= 1 << 8 {
328            c = (1 << 8) - 1;
329        }
330
331        *ret = c as u8;
332
333        Ok(())
334    } else {
335        let data_len = data.len();
336        Err(Error::Quant(format!(
337            "pos({pos}) * depth({depth}) is out-of-bounds, max value: {data_len})"
338        )))
339    }
340}
341
342type DiffuseFn = fn(&mut [u8], i32, i32, i32, i32, i32, i32) -> Result<()>;
343
344/// No-op diffuse function.
345pub fn diffuse_none(
346    _data: &mut [u8],
347    _width: i32,
348    _height: i32,
349    _x: i32,
350    _y: i32,
351    _depth: i32,
352    _error: i32,
353) -> Result<()> {
354    Ok(())
355}
356
357/// Floyd-Steinberg Method
358///
359/// | Col0 | Col1 | Col2 |
360/// |------|------|------|
361/// |      | curr | 7/16 |
362/// | 3/16 | 5/48 | 1/16 |
363pub fn diffuse_fs(
364    data: &mut [u8],
365    width: i32,
366    height: i32,
367    x: i32,
368    y: i32,
369    depth: i32,
370    error: i32,
371) -> Result<()> {
372    let pos = y.saturating_mul(width).saturating_add(x);
373
374    if x < width.saturating_sub(1) && y < height.saturating_sub(1) {
375        // add error to the right cell
376        error_diffuse(
377            data,
378            pos.saturating_add(1),
379            depth,
380            error,
381            7,
382            16,
383            width.saturating_mul(height),
384        )?;
385        // add error to the left-bottom cell
386        error_diffuse(
387            data,
388            pos.saturating_add(width.saturating_sub(1)),
389            depth,
390            error,
391            3,
392            16,
393            width.saturating_mul(height),
394        )?;
395        // add error to the bottom cell
396        error_diffuse(
397            data,
398            pos.saturating_add(width),
399            depth,
400            error,
401            5,
402            16,
403            width.saturating_mul(height),
404        )?;
405        // add error to the right-bottom cell
406        error_diffuse(
407            data,
408            pos.saturating_add(width.saturating_add(1)),
409            depth,
410            error,
411            1,
412            16,
413            width.saturating_mul(height),
414        )?;
415    }
416
417    Ok(())
418}
419
420/// Atkinson's Method
421///
422/// | Col0 | Col1 | Col2 | Col3 |
423/// |------|------|------|------|
424/// |      | curr | 1/8  | 1/8  |
425/// | 1/8  | 1/8  | 1/8  |      |
426/// |      | 1/8  |      |      |
427pub fn diffuse_atkinson(
428    data: &mut [u8],
429    width: i32,
430    height: i32,
431    x: i32,
432    y: i32,
433    depth: i32,
434    error: i32,
435) -> Result<()> {
436    let pos = y.saturating_mul(width).saturating_add(x);
437
438    if y < height.saturating_sub(2) {
439        let area = width.saturating_mul(height);
440
441        // add error to the right cell
442        error_diffuse(data, pos.saturating_add(1), depth, error, 1, 8, area)?;
443        // add error to the 2nd right cell
444        error_diffuse(data, pos.saturating_add(2), depth, error, 1, 8, area)?;
445        // add error to the left-bottom cell
446        error_diffuse(
447            data,
448            pos.saturating_add(width.saturating_sub(1)),
449            depth,
450            error,
451            1,
452            8,
453            area,
454        )?;
455        // add error to the bottom cell
456        error_diffuse(data, pos.saturating_add(width), depth, error, 1, 8, area)?;
457        // add error to the right-bottom cell
458        error_diffuse(
459            data,
460            pos.saturating_add(width.saturating_add(1)),
461            depth,
462            error,
463            1,
464            8,
465            area,
466        )?;
467        // add error to the 2nd bottom cell
468        error_diffuse(
469            data,
470            pos.saturating_add(width.saturating_mul(2)),
471            depth,
472            error,
473            1,
474            8,
475            area,
476        )?;
477    }
478
479    Ok(())
480}
481
482/// Jarvis, Judice & Ninks Method
483///
484/// | Col0 | Col1 | Col2 | Col3 | Col4 |
485/// |------|------|------|------|------|
486/// |      |      | curr | 7/48 | 5/48 |
487/// | 3/48 | 5/48 | 7/48 | 5/48 | 3/48 |
488/// | 1/48 | 3/48 | 5/48 | 3/48 | 1/48 |
489pub fn diffuse_jajuni(
490    data: &mut [u8],
491    width: i32,
492    height: i32,
493    x: i32,
494    y: i32,
495    depth: i32,
496    error: i32,
497) -> Result<()> {
498    let pos = y.saturating_mul(width).saturating_add(x);
499
500    if y < height.saturating_sub(2) {
501        let area = width.saturating_mul(height);
502        let width2 = width.saturating_mul(2);
503
504        error_diffuse(data, pos.saturating_add(1), depth, error, 7, 48, area)?;
505        error_diffuse(data, pos.saturating_add(2), depth, error, 5, 48, area)?;
506        error_diffuse(
507            data,
508            pos.saturating_add(width.saturating_sub(2)),
509            depth,
510            error,
511            3,
512            48,
513            area,
514        )?;
515        error_diffuse(
516            data,
517            pos.saturating_add(width.saturating_sub(1)),
518            depth,
519            error,
520            5,
521            48,
522            area,
523        )?;
524        error_diffuse(data, pos.saturating_add(width), depth, error, 7, 48, area)?;
525        error_diffuse(
526            data,
527            pos.saturating_add(width.saturating_add(1)),
528            depth,
529            error,
530            5,
531            48,
532            area,
533        )?;
534        error_diffuse(
535            data,
536            pos.saturating_add(width.saturating_add(2)),
537            depth,
538            error,
539            3,
540            48,
541            area,
542        )?;
543        error_diffuse(
544            data,
545            pos.saturating_add(width2.saturating_sub(2)),
546            depth,
547            error,
548            1,
549            48,
550            area,
551        )?;
552        error_diffuse(
553            data,
554            pos.saturating_add(width2.saturating_sub(1)),
555            depth,
556            error,
557            3,
558            48,
559            area,
560        )?;
561        error_diffuse(data, pos.saturating_add(width2), depth, error, 5, 48, area)?;
562        error_diffuse(
563            data,
564            pos.saturating_add(width2.saturating_add(1)),
565            depth,
566            error,
567            3,
568            48,
569            area,
570        )?;
571        error_diffuse(
572            data,
573            pos.saturating_add(width2.saturating_add(2)),
574            depth,
575            error,
576            1,
577            48,
578            area,
579        )?;
580    }
581
582    Ok(())
583}
584
585/// Stucki's Method
586///
587/// | Col0 | Col1 | Col2 | Col3 | Col4 |
588/// |------|------|------|------|------|
589/// |      |      | curr | 8/48 | 4/48 |
590/// | 2/48 | 4/48 | 8/48 | 4/48 | 2/48 |
591/// | 1/48 | 2/48 | 4/48 | 2/48 | 1/48 |
592pub fn diffuse_stucki(
593    data: &mut [u8],
594    width: i32,
595    height: i32,
596    x: i32,
597    y: i32,
598    depth: i32,
599    error: i32,
600) -> Result<()> {
601    let pos = y.saturating_mul(width).saturating_add(x);
602
603    if y < height.saturating_sub(2) {
604        let area = width.saturating_mul(height);
605        let width2 = width.saturating_mul(2);
606
607        error_diffuse(data, pos.saturating_add(1), depth, error, 1, 6, area)?;
608        error_diffuse(data, pos.saturating_add(2), depth, error, 1, 12, area)?;
609        error_diffuse(
610            data,
611            pos.saturating_add(width.saturating_sub(2)),
612            depth,
613            error,
614            1,
615            24,
616            area,
617        )?;
618        error_diffuse(
619            data,
620            pos.saturating_add(width.saturating_sub(1)),
621            depth,
622            error,
623            1,
624            12,
625            area,
626        )?;
627        error_diffuse(data, pos.saturating_add(width), depth, error, 1, 6, area)?;
628        error_diffuse(
629            data,
630            pos.saturating_add(width.saturating_add(1)),
631            depth,
632            error,
633            1,
634            12,
635            area,
636        )?;
637        error_diffuse(
638            data,
639            pos.saturating_add(width.saturating_add(2)),
640            depth,
641            error,
642            1,
643            24,
644            area,
645        )?;
646        error_diffuse(
647            data,
648            pos.saturating_add(width2.saturating_sub(2)),
649            depth,
650            error,
651            1,
652            48,
653            area,
654        )?;
655        error_diffuse(
656            data,
657            pos.saturating_add(width2.saturating_sub(1)),
658            depth,
659            error,
660            1,
661            24,
662            area,
663        )?;
664        error_diffuse(data, pos.saturating_add(width2), depth, error, 1, 12, area)?;
665        error_diffuse(
666            data,
667            pos.saturating_add(width2.saturating_add(1)),
668            depth,
669            error,
670            1,
671            24,
672            area,
673        )?;
674        error_diffuse(
675            data,
676            pos.saturating_add(width2.saturating_add(2)),
677            depth,
678            error,
679            1,
680            48,
681            area,
682        )?;
683    }
684
685    Ok(())
686}
687
688/// Burkes' Method
689///
690/// | Col0 | Col1 | Col2 | Col3 | Col4 |
691/// |------|------|------|------|------|
692/// |      |      | curr | 4/16 | 2/16 |
693/// | 1/16 | 2/16 | 4/16 | 2/16 | 1/16 |
694pub fn diffuse_burkes(
695    data: &mut [u8],
696    width: i32,
697    height: i32,
698    x: i32,
699    y: i32,
700    depth: i32,
701    error: i32,
702) -> Result<()> {
703    let pos = y.saturating_mul(width).saturating_add(x);
704
705    if y < height.saturating_sub(2) {
706        let area = width.saturating_mul(height);
707
708        error_diffuse(data, pos.saturating_add(1), depth, error, 1, 4, area)?;
709        error_diffuse(data, pos.saturating_add(2), depth, error, 1, 8, area)?;
710        error_diffuse(
711            data,
712            pos.saturating_add(width.saturating_sub(2)),
713            depth,
714            error,
715            1,
716            16,
717            area,
718        )?;
719        error_diffuse(
720            data,
721            pos.saturating_add(width.saturating_sub(1)),
722            depth,
723            error,
724            1,
725            8,
726            area,
727        )?;
728        error_diffuse(data, pos.saturating_add(width), depth, error, 1, 4, area)?;
729        error_diffuse(
730            data,
731            pos.saturating_add(width.saturating_add(1)),
732            depth,
733            error,
734            1,
735            8,
736            area,
737        )?;
738        error_diffuse(
739            data,
740            pos.saturating_add(width.saturating_add(2)),
741            depth,
742            error,
743            1,
744            16,
745            area,
746        )?;
747    }
748
749    Ok(())
750}
751
752type MaskFn = fn(i32, i32, i32) -> f32;
753
754fn mask_a(x: i32, y: i32, c: i32) -> f32 {
755    let x_mul = x.saturating_add(c.saturating_mul(67));
756    let y_mul = y.saturating_mul(236);
757
758    ((x_mul.saturating_add(y_mul).saturating_mul(119) & 255) as f32) / 128f32 - 1f32
759}
760
761fn mask_x(x: i32, y: i32, c: i32) -> f32 {
762    let x_mul = x.saturating_add(c.saturating_mul(29));
763    let y_mul = y.saturating_mul(149);
764
765    ((x_mul.saturating_add(y_mul).saturating_mul(1234) & 511) as f32) / 256f32 - 1f32
766}
767
768fn mask_none(_x: i32, _y: i32, _c: i32) -> f32 {
769    0.0
770}
771
772type LookupFn = fn(&[u8], usize, &[u8], usize, &mut [u16], i32) -> Option<usize>;
773
774/// No-op lookup function
775pub fn lookup_none(
776    _pixel: &[u8],
777    _depth: usize,
778    _palette: &[u8],
779    _req_color: usize,
780    _cache_table: &mut [u16],
781    _complexion: i32,
782) -> Option<usize> {
783    None
784}
785
786/// Performs lookup for the closest color from palette using `"normal"` strategy.
787pub fn lookup_normal(
788    pixel: &[u8],
789    depth: usize,
790    palette: &[u8],
791    req_color: usize,
792    _cache_table: &mut [u16],
793    complexion: i32,
794) -> Option<usize> {
795    let max_palette = req_color.saturating_mul(depth).saturating_add(depth);
796
797    if palette.len() < max_palette || pixel.len() < depth || max_palette == usize::MAX {
798        None
799    } else {
800        let mut result = None;
801        let mut diff = i32::MAX;
802
803        for i in 0..req_color {
804            let r = pixel[0].saturating_sub(palette[i * depth]) as i32;
805
806            let mut distant = r.saturating_mul(r).saturating_mul(complexion);
807
808            for n in 1..depth {
809                let r = pixel[n].saturating_sub(palette[i * depth + n]) as i32;
810
811                distant = distant.saturating_add(r.saturating_mul(r));
812            }
813
814            if distant < diff {
815                diff = distant;
816                result = Some(i);
817            }
818        }
819
820        result
821    }
822}
823
824/// Performs lookup for the closest color from palette using `"fast"` strategy.
825pub fn lookup_fast(
826    pixel: &[u8],
827    _depth: usize,
828    palette: &[u8],
829    req_color: usize,
830    cache_table: &mut [u16],
831    complexion: i32,
832) -> Option<usize> {
833    let depth = 3;
834    let max_palette = req_color.saturating_mul(depth).saturating_add(depth);
835
836    if palette.len() < max_palette || pixel.len() < depth {
837        None
838    } else {
839        let hash = compute_hash(pixel, depth);
840        let cache = cache_table.get(hash).unwrap_or(&0);
841
842        if *cache > 0 {
843            // fast lookup
844            Some((cache - 1) as usize)
845        } else {
846            let mut result = None;
847            let mut diff = i32::MAX;
848
849            for i in 0..req_color {
850                // complexion correction
851                let p0 = (pixel[0] as i32)
852                    .saturating_sub(palette[i * depth] as i32)
853                    .saturating_mul(pixel[0].saturating_sub(palette[i * depth]) as i32)
854                    .saturating_mul(complexion);
855
856                let p1 = (pixel[1] as i32)
857                    .saturating_sub(palette[i * depth + 1] as i32)
858                    .saturating_mul(pixel[1].saturating_sub(palette[i * depth + 1]) as i32);
859
860                let p2 = (pixel[2] as i32)
861                    .saturating_sub(palette[i * depth + 1] as i32)
862                    .saturating_mul(pixel[2].saturating_sub(palette[i * depth + 2]) as i32);
863
864                let distant = p0.saturating_add(p1).saturating_add(p2);
865
866                if distant < diff {
867                    diff = distant;
868                    result = Some(i);
869                }
870            }
871
872            if let Some(r) = result {
873                cache_table[hash] = (r + 1) as u16;
874            }
875
876            result
877        }
878    }
879}
880
881/// Performs lookup for the closest color from mono dark background palette.
882pub fn lookup_mono_darkbg(
883    pixel: &[u8],
884    depth: usize,
885    _palette: &[u8],
886    req_color: usize,
887    _cache_table: &mut [u16],
888    _complexion: i32,
889) -> Option<usize> {
890    let distant: usize = pixel[..depth].iter().map(|&p| p as usize).sum();
891
892    if distant >= req_color.saturating_mul(128) {
893        Some(1)
894    } else {
895        Some(0)
896    }
897}
898
899/// Performs lookup for the closest color from mono light background palette.
900pub fn lookup_mono_lightbg(
901    pixel: &[u8],
902    depth: usize,
903    _palette: &[u8],
904    req_color: usize,
905    _cache_table: &mut [u16],
906    _complexion: i32,
907) -> Option<usize> {
908    let distant: usize = pixel[..depth].iter().map(|&p| p as usize).sum();
909
910    if distant < req_color.saturating_mul(128) {
911        Some(1)
912    } else {
913        Some(0)
914    }
915}
916
917impl SixelDither {
918    const MAX_DEPTH: usize = 4;
919
920    /// Creates a color palette.
921    pub fn make_palette(&mut self, data: &[u8]) -> Result<Vec<u8>> {
922        let depth = self.pixel_format.compute_depth();
923
924        let color_map = ColorMap::compute_from_input(
925            data,
926            depth,
927            self.req_colors,
928            self.method_for_largest,
929            self.method_for_rep,
930            self.quality_mode,
931            &mut self.orig_colors,
932        )?;
933
934        self.ncolors = color_map.table.len();
935
936        let mut result: Vec<u8> = Vec::with_capacity(self.ncolors.wrapping_mul(depth));
937
938        let colors = self.ncolors;
939        for t in color_map.table[..colors].iter() {
940            for &tuple in t.tuple[..depth].iter() {
941                result.push(tuple as u8);
942            }
943        }
944
945        Ok(result)
946    }
947
948    /// Applies color palette into the specified pixel buffers.
949    pub fn quant_apply_palette(&mut self, data: &mut [u8]) -> Result<Vec<u8>> {
950        let (height, width, depth) = (self.height, self.width, self.depth);
951
952        if self.req_colors < 1 {
953            Err(Error::Quant("apply_palette: req_color == 0".into()))
954        } else if depth > Self::MAX_DEPTH {
955            let max = Self::MAX_DEPTH;
956
957            Err(Error::Quant(format!(
958                "apply_palette: depth is out-of-bounds: {depth}, max: {max}"
959            )))
960        } else {
961            let mut result = vec![0u8; width * height * depth];
962
963            let (f_mask, f_diffuse): (MaskFn, DiffuseFn) = match self.method_for_diffuse {
964                MethodForDiffuse::None => (mask_none, diffuse_none),
965                MethodForDiffuse::Atkinson => (mask_none, diffuse_atkinson),
966                MethodForDiffuse::Fs => (mask_none, diffuse_fs),
967                MethodForDiffuse::Jajuni => (mask_none, diffuse_jajuni),
968                MethodForDiffuse::Stucki => (mask_none, diffuse_stucki),
969                MethodForDiffuse::Burkes => (mask_none, diffuse_burkes),
970                MethodForDiffuse::ADither => (mask_a, diffuse_none),
971                MethodForDiffuse::XDither => (mask_x, diffuse_none),
972                MethodForDiffuse::Auto => {
973                    return Err(Error::Quant(
974                        "apply_palette: invalid method_for_diffuse(Auto)".into(),
975                    ))
976                }
977            };
978
979            let (f_lookup, lookup_fn): (LookupFn, LookupFunction) = if self.req_colors == 2 {
980                let sum1: u32 = self.palette[..depth].iter().map(|&p| u32::from(p)).sum();
981                let sum2: u32 = self.palette[..depth + depth]
982                    .iter()
983                    .map(|&p| u32::from(p))
984                    .sum();
985
986                if sum1 == 0 && sum2 == 255 * 3 {
987                    (lookup_mono_darkbg, LookupFunction::MonoDarkBg)
988                } else if sum1 == 255 * 3 && sum2 == 0 {
989                    (lookup_mono_lightbg, LookupFunction::MonoLightBg)
990                } else if self.optimized && depth == 3 {
991                    (lookup_fast, LookupFunction::Fast)
992                } else {
993                    (lookup_normal, LookupFunction::Normal)
994                }
995            } else if self.optimized && depth == 3 {
996                (lookup_fast, LookupFunction::Fast)
997            } else {
998                (lookup_normal, LookupFunction::Normal)
999            };
1000
1001            let mut index_table = self.cache_table.clone();
1002            if index_table.is_empty() && lookup_fn == LookupFunction::Fast {
1003                index_table.resize(1usize << (depth * 5), 0u16);
1004            }
1005
1006            if self.optimize_palette {
1007                self.ncolors = 0;
1008
1009                let mut new_palette = vec![0u8; PALETTE_MAX * depth];
1010                let mut migration_map = vec![0u16; PALETTE_MAX];
1011
1012                let max_area = height.saturating_mul(width).saturating_add(width);
1013                let max_depth = max_area.saturating_mul(depth).saturating_add(depth);
1014                let data_len = data.len();
1015
1016                if data_len < max_area || data_len < max_depth {
1017                    return Err(Error::Quant(format!("apply_palette: max area and/or depth are out-of-bounds: max area: {max_area}, max depth: {max_depth}, data length: {data_len}")));
1018                }
1019
1020                if self.method_for_diffuse.is_mask() {
1021                    self.apply_mask_optimize(
1022                        data,
1023                        new_palette.as_mut(),
1024                        result.as_mut(),
1025                        migration_map.as_mut(),
1026                        index_table.as_mut(),
1027                        (f_mask, f_lookup),
1028                    )?;
1029                } else {
1030                    self.apply_no_mask_optimize(
1031                        data,
1032                        new_palette.as_mut(),
1033                        result.as_mut(),
1034                        migration_map.as_mut(),
1035                        index_table.as_mut(),
1036                        (f_lookup, f_diffuse),
1037                    )?;
1038                }
1039            } else {
1040                if self.method_for_diffuse.is_mask() {
1041                    self.apply_mask(
1042                        data,
1043                        result.as_mut(),
1044                        index_table.as_mut(),
1045                        (f_mask, f_lookup),
1046                    )?;
1047                } else {
1048                    self.apply_no_mask(
1049                        data,
1050                        result.as_mut(),
1051                        index_table.as_mut(),
1052                        (f_lookup, f_diffuse),
1053                    )?;
1054                }
1055
1056                self.ncolors = self.req_colors;
1057            }
1058
1059            self.cache_table = index_table;
1060
1061            Ok(result)
1062        }
1063    }
1064
1065    fn apply_mask_optimize(
1066        &mut self,
1067        data: &mut [u8],
1068        new_palette: &mut [u8],
1069        result: &mut [u8],
1070        migration_map: &mut [u16],
1071        index_table: &mut [u16],
1072        apply_fns: (MaskFn, LookupFn),
1073    ) -> Result<()> {
1074        let (height, width, depth) = (self.height, self.width, self.depth);
1075        let (f_mask, f_lookup) = apply_fns;
1076
1077        for y in 0..height {
1078            for x in 0..width {
1079                let mut copy = vec![0u8; Self::MAX_DEPTH];
1080
1081                let pos = y * width + x;
1082                for d in 0..depth {
1083                    let val = data[pos * depth + d] as f32
1084                        + (f_mask(x as i32, y as i32, d as i32) * 32.0);
1085                    copy[d] = if val.is_nan() || val.is_infinite() || val < 0.0 {
1086                        0
1087                    } else if val > 255.0 {
1088                        255
1089                    } else {
1090                        val as u8
1091                    }
1092                }
1093
1094                let color_index = f_lookup(
1095                    copy.as_ref(),
1096                    depth,
1097                    self.palette.as_mut(),
1098                    self.req_colors,
1099                    index_table,
1100                    self.complexion,
1101                )
1102                .unwrap_or(0);
1103
1104                if migration_map[color_index] == 0 {
1105                    result[pos] = self.ncolors as u8;
1106                    for n in 0..depth {
1107                        new_palette[self.ncolors * depth + n] =
1108                            self.palette[color_index * depth + n];
1109                    }
1110                    self.ncolors += 1;
1111                    migration_map[color_index] = self.ncolors as u16;
1112                } else {
1113                    result[pos] = (migration_map[color_index] - 1) as u8;
1114                }
1115            }
1116        }
1117
1118        let copy_len = self.ncolors * depth;
1119        self.palette[..copy_len].copy_from_slice(new_palette[..copy_len].as_ref());
1120
1121        Ok(())
1122    }
1123
1124    fn apply_no_mask_optimize(
1125        &mut self,
1126        data: &mut [u8],
1127        new_palette: &mut [u8],
1128        result: &mut [u8],
1129        migration_map: &mut [u16],
1130        index_table: &mut [u16],
1131        apply_fns: (LookupFn, DiffuseFn),
1132    ) -> Result<()> {
1133        let (height, width, depth) = (self.height, self.width, self.depth);
1134        let (f_lookup, f_diffuse) = apply_fns;
1135
1136        for y in 0..height {
1137            for x in 0..width {
1138                let pos = y * width + x;
1139                let color_index = f_lookup(
1140                    data[pos * depth..].as_mut(),
1141                    depth,
1142                    self.palette.as_mut(),
1143                    self.req_colors,
1144                    index_table,
1145                    self.complexion,
1146                )
1147                .unwrap_or(0);
1148
1149                if migration_map[color_index] == 0 {
1150                    result[pos] = self.ncolors as u8;
1151
1152                    for n in 0..depth {
1153                        new_palette[self.ncolors * depth + n] =
1154                            self.palette[color_index * depth + n];
1155                    }
1156                    self.ncolors += 1;
1157                    migration_map[color_index] = self.ncolors as u16;
1158                } else {
1159                    result[pos] = (migration_map[color_index] - 1) as u8;
1160                }
1161
1162                for n in 0..depth {
1163                    let offset =
1164                        data[pos * depth + n].saturating_sub(self.palette[color_index * depth + n]);
1165
1166                    f_diffuse(
1167                        data[n..].as_mut(),
1168                        width as i32,
1169                        height as i32,
1170                        x as i32,
1171                        y as i32,
1172                        depth as i32,
1173                        offset as i32,
1174                    )?;
1175                }
1176            }
1177        }
1178
1179        let copy_len = self.ncolors * depth;
1180        self.palette[..copy_len].copy_from_slice(new_palette[..copy_len].as_ref());
1181
1182        Ok(())
1183    }
1184
1185    fn apply_mask(
1186        &mut self,
1187        data: &mut [u8],
1188        result: &mut [u8],
1189        index_table: &mut [u16],
1190        apply_fns: (MaskFn, LookupFn),
1191    ) -> Result<()> {
1192        let (height, width, depth) = (self.height, self.width, self.depth);
1193        let (f_mask, f_lookup) = apply_fns;
1194
1195        for y in 0..height {
1196            for x in 0..width {
1197                let mut copy = vec![0u8; Self::MAX_DEPTH];
1198                let pos = y * width + x;
1199                for d in 0..depth {
1200                    let val =
1201                        data[pos * depth + d] as f32 + f_mask(x as i32, y as i32, d as i32) * 32.0;
1202                    copy[d] = if val.is_nan() || val.is_infinite() || val < 0.0 {
1203                        0
1204                    } else if val > 255.0 {
1205                        255
1206                    } else {
1207                        val as u8
1208                    };
1209
1210                    result[pos] = f_lookup(
1211                        copy.as_ref(),
1212                        depth,
1213                        self.palette.as_mut(),
1214                        self.req_colors,
1215                        index_table,
1216                        self.complexion,
1217                    )
1218                    .unwrap_or(0) as u8;
1219                }
1220            }
1221        }
1222
1223        Ok(())
1224    }
1225
1226    fn apply_no_mask(
1227        &mut self,
1228        data: &mut [u8],
1229        result: &mut [u8],
1230        index_table: &mut [u16],
1231        apply_fns: (LookupFn, DiffuseFn),
1232    ) -> Result<()> {
1233        let (height, width, depth) = (self.height, self.width, self.depth);
1234        let (f_lookup, f_diffuse) = apply_fns;
1235
1236        for y in 0..height {
1237            for x in 0..width {
1238                let pos = y * width + x;
1239                let color_index = f_lookup(
1240                    data[pos * depth..].as_ref(),
1241                    depth,
1242                    self.palette.as_mut(),
1243                    self.req_colors,
1244                    index_table,
1245                    self.complexion,
1246                )
1247                .unwrap_or(0);
1248
1249                result[pos] = color_index as u8;
1250
1251                for n in 0..depth {
1252                    let offset =
1253                        data[pos * depth + n].saturating_sub(self.palette[color_index * depth + n]);
1254
1255                    f_diffuse(
1256                        data[n..].as_mut(),
1257                        width as i32,
1258                        height as i32,
1259                        x as i32,
1260                        y as i32,
1261                        depth as i32,
1262                        offset as i32,
1263                    )?;
1264                }
1265            }
1266        }
1267
1268        Ok(())
1269    }
1270}