devela/media/image/sixel/quant/
mod.rs

1// devela::media::image::sixel::quant
2
3#![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/// TODO
14#[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    /// Comparing function to be used for ordering
22    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/*
34 ** Here is the fun part, the median-cut colormap generator.  This is based
35 ** on Paul Heckbert's paper "Color Image Quantization for Frame Buffer
36 ** Display", SIGGRAPH '82 Proceedings, page 297.
37 */
38
39/// TODO
40#[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/// TODO
50#[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    /* Set up the initial box. */
55    result[0].ind = 0;
56    result[0].colors = colors;
57    result[0].sum = sum;
58
59    result
60}
61
62/// Go through the box finding the minimum and maximum of each
63/// component - the boundaries of the box.
64fn 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/// TODO
86#[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/// This function presumes that the tuple type is either
102/// BLACKANDWHITE, GRAYSCALE, or RGB (which implies pamP->depth is 1 or 3).
103/// To save time, we don't actually check it.
104#[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        /* An RGB tuple */
114        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
129/// TODO
130fn 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
150/// TODO
151fn 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
169/// Number of tuples represented by the box
170/// Count the tuples in question
171fn 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; /* initial value */
179    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
193/// Ok, we've got enough boxes. Now choose a representative color for each box.
194///
195/// There are a number of possible ways to make this choice.
196/// - One would be to choose the center of the box; this ignores any structure
197///   within the boxes.
198/// - Another method would be to average all the colors in the box.
199///   This is the method specified in Heckbert's paper.
200/// - A third method is to average all the pixels in the box.
201fn 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
245/// Split Box 'bi' in the box vector bv (so that bv contains one more box
246/// than it did as input).  Split it so that each new box represents about
247/// half of the pixels in the distribution given by 'colorfreqtable' for
248/// the colors in the original box, but with distinct colors in each of the
249/// two new boxes.
250///
251/// Assume the box contains at least two colors.
252fn 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    /* assert(max_depth >= depth); */
269
270    find_box_boundaries(colorfreqtable, depth, box_start, box_size, &mut minval, &mut maxval);
271
272    /* Find the largest dimension, and sort by that component.  I have
273       included two methods for determining the "largest" dimension;
274       first by simply comparing the range in RGB space, and second by
275       transforming into luminosities before the comparison.
276    */
277    // let _largest_dimension = match _largest {
278    //     SixelSplit::Auto | SixelSplit::Norm => largest_by_norm(&minval, &maxval, depth),
279    //     SixelSplit::Lum => largest_by_luminosity(&minval, &maxval, depth),
280    // };
281
282    /* TODO: I think this sort should go after creating a box,
283       not before splitting.  Because you need the sort to use
284       the SIXEL_REP_CENTER_BOX method of choosing a color to
285       represent the final boxes
286    */
287
288    /* Set the gross global variable 'compareplanePlane' as a
289       parameter to compareplane(), which is called by qsort().
290    */
291
292    /* Sholdn't be needed - I use a stupid hasmap - should be refactored.
293    compareplanePlane = largest_dimension;
294    qsort((char*) &colorfreqtable.table[box_start], box_size,
295          sizeof(colorfreqtable.table[box_start]),
296          compareplane);*/
297
298    /* Now find the median based on the counts, so that about half
299    the pixels (not colors, pixels) are in each subdivision.  */
300    let mut lowersum = colorfreqtable.get(&box_start).unwrap().value; /* initial value */
301    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    /* Split the box, and sort to bring the biggest boxes to the top.  */
308
309    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
320/// Compute a set of only 'newcolors' colors that best represent an
321/// image whose pixels are summarized by the histogram
322/// 'colorfreqtable'.  Each tuple in that table has depth 'depth'.
323/// colorfreqtable.table\[i\] tells the number of pixels in the subject image
324/// have a particular color.
325///
326/// As a side effect, sort 'colorfreqtable'.
327fn 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    // There is at least one box that contains at least 2 colors; ergo,
342    // there is more splitting we can do.
343    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    // Main loop: split boxes until we have enough.
348    while boxes < newcolors && multi_color_boxes_exist {
349        // Find the first splittable box.
350        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
366/// TODO
367fn 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/// TODO
376#[derive(Clone)]
377struct Tuple {
378    pub value: i32,
379    pub tuple: Vec<i32>,
380}
381
382/// TODO
383fn 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/// Produce a colormap containing the best colors to represent the
441/// image stream in file 'ifP'.  Figure it out using the median cut
442/// technique.
443///
444/// The colormap will have 'req_colors' or fewer colors in it, unless
445/// 'allcolors' is true, in which case it will have all the colors that
446/// are in the input.
447///
448/// The colormap has the same maxval as the input.
449///
450/// Put the colormap in newly allocated storage as a tupletable2
451/// and return its address as *colormap.  Return the number of colors in
452/// it as *colorsP and its maxval as *colormapMaxvalP.
453///
454/// Return the characteristics of the input file as
455/// *formatP and *freqPamP.  (This information is not really
456/// relevant to our colormap mission; just a fringe benefit).
457#[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        /*
474        for i in colorfreqtable.len() as i32..=req_colors {
475            let mut tuple: Vec<i32> = vec![0; depth as usize];
476            for n in 0..depth {
477                tuple[n as usize] = (i * depth) + n;
478            }
479            colorfreqtable.insert(i, Tuple { value: i, tuple });
480        }*/
481
482        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/// No diffusion.
492#[rustfmt::skip]
493fn diffuse_none(_: &mut [u8], _: i32, _h: i32, _x: i32, _y: i32, _: i32, _: i32) {}
494
495/// TODO
496#[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/// TODO
502#[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/// Lookup closest color from palette with "normal" strategy.
508#[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    /* don't use cachetable in 'normal' strategy */
521
522    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
539/// lookup closest color from palette with "fast" strategy.
540fn 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        /* fast lookup */
555        return cache as i32 - 1;
556    }
557    /* collision */
558    for i in 0..reqcolor {
559        /*          distant = 0;
560         #if 0
561                for (n = 0; n < 3; ++n) {
562                    r = pixel[n] - palette[i * 3 + n];
563                    distant += r * r;
564                }
565        #elif 1*/
566        /* complexion correction */
567        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        //  #endif
576        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
586/// TODO
587fn 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
602/// TODO
603fn 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/// Choose colors using median-cut method.
619//
620// Called from DitherConf::initialize
621#[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    // if (result_depth <= 0) { *result = NULL; goto end; }
635
636    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/// Apply color palette to the given pixel buffer.
659//
660// Called from DitherConf::initialize
661#[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    // check bad reqcolor
678    if reqcolor < 1 {
679        // sixel_helper_set_additional_message(
680        // "sixel_quant_apply_palette: "
681        // "a bad argument is detected, reqcolor < 0.");
682        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                    //                    &[u8],  i32,   &[u8],    i32,    &mut [u16],    i32
759                    let color_index =
760                        f_lookup.unwrap()(&copy, 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}