icy_sixel/
quant.rs

1#![allow(clippy::erasing_op)]
2/*****************************************************************************
3 *
4 * quantization
5 *
6 *****************************************************************************/
7
8use std::cmp::Ordering;
9use std::vec;
10
11#[derive(Clone)]
12struct bbox {
13    pub ind: i32,
14    pub colors: i32,
15    pub sum: i32,
16}
17
18/*
19typedef struct box* boxVector;
20
21typedef unsigned long sample;
22typedef sample * tuple;
23
24struct tupleint {
25    /* An ordered pair of a tuple value and an integer, such as you
26       would find in a tuple table or tuple hash.
27       Note that this is a variable length structure.
28    */
29    unsigned int value;
30    sample tuple[1];
31    /* This is actually a variable size array -- its size is the
32       depth of the tuple in question.  Some compilers do not let us
33       declare a variable length array.
34    */
35};
36typedef struct tupleint ** tupletable;
37
38typedef struct {
39    unsigned int size;
40    tupletable table;
41} tupletable2;
42
43static unsigned int compareplanePlane;
44
45*/
46/* This is a parameter to compareplane().  We use this global variable
47   so that compareplane() can be called by qsort(), to compare two
48   tuples.  qsort() doesn't pass any arguments except the two tuples.
49*/
50/*
51static int
52compareplane(const void * const arg1,
53             const void * const arg2)
54{
55    int lhs, rhs;
56
57    typedef const struct tupleint * const * const sortarg;
58    sortarg comparandPP  = (sortarg) arg1;
59    sortarg comparatorPP = (sortarg) arg2;
60    lhs = (int)(*comparandPP)->tuple[compareplanePlane];
61    rhs = (int)(*comparatorPP)->tuple[compareplanePlane];
62
63    return lhs - rhs;
64}*/
65
66fn sumcompare(b1: &bbox, b2: &bbox) -> Ordering {
67    b2.sum.cmp(&b1.sum)
68}
69
70/*
71 ** Here is the fun part, the median-cut colormap generator.  This is based
72 ** on Paul Heckbert's paper "Color Image Quantization for Frame Buffer
73 ** Display", SIGGRAPH '82 Proceedings, page 297.
74 */
75
76pub fn newColorMap(newcolors: i32, depth: i32) -> HashMap<i32, Tuple> {
77    let mut colormap = HashMap::new();
78    for i in 0..newcolors {
79        colormap.insert(
80            i,
81            Tuple {
82                value: 0,
83                tuple: vec![0; depth as usize],
84            },
85        );
86    }
87    colormap
88}
89
90fn newBoxVector(colors: i32, sum: i32, newcolors: i32) -> Vec<bbox> {
91    let mut result = vec![
92        bbox {
93            ind: 0,
94            colors: 0,
95            sum: 0
96        };
97        newcolors as usize
98    ];
99
100    /* Set up the initial box. */
101    result[0].ind = 0;
102    result[0].colors = colors;
103    result[0].sum = sum;
104
105    result
106}
107
108pub fn findBoxBoundaries(
109    colorfreqtable: &mut HashMap<i32, Tuple>,
110    depth: i32,
111    boxStart: i32,
112    boxSize: i32,
113    minval: &mut [i32],
114    maxval: &mut [i32],
115) {
116    /*----------------------------------------------------------------------------
117      Go through the box finding the minimum and maximum of each
118      component - the boundaries of the box.
119    -----------------------------------------------------------------------------*/
120
121    for plane in 0..depth {
122        minval[plane as usize] = colorfreqtable.get(&(boxStart)).unwrap().tuple[plane as usize];
123        maxval[plane as usize] = minval[plane as usize];
124    }
125    for i in 1..boxSize {
126        for plane in 0..depth {
127            let v = colorfreqtable.get(&(boxStart + i)).unwrap().tuple[plane as usize];
128            minval[plane as usize] = minval[plane as usize].min(v);
129            maxval[plane as usize] = maxval[plane as usize].max(v);
130        }
131    }
132}
133
134pub fn largestByNorm(minval: &[i32], maxval: &[i32], depth: i32) -> i32 {
135    let mut largestSpreadSoFar = 0;
136    let mut largestDimension = 0;
137    for plane in 0..depth as usize {
138        let spread = maxval[plane] - minval[plane];
139        if spread > largestSpreadSoFar {
140            largestDimension = plane;
141            largestSpreadSoFar = spread;
142        }
143    }
144    largestDimension as i32
145}
146
147pub fn largestByLuminosity(minval: &[i32], maxval: &[i32], depth: i32) -> i32 {
148    /*----------------------------------------------------------------------------
149       This subroutine presumes that the tuple type is either
150       BLACKANDWHITE, GRAYSCALE, or RGB (which implies pamP->depth is 1 or 3).
151       To save time, we don't actually check it.
152    -----------------------------------------------------------------------------*/
153    let retval;
154
155    let lumin_factor = [0.2989, 0.5866, 0.1145];
156
157    if depth == 1 {
158        retval = 0;
159    } else {
160        /* An RGB tuple */
161        let mut largestSpreadSoFar = 0.0;
162        let mut largestDimension = 0;
163
164        for plane in 0..3 {
165            let spread = lumin_factor[plane] * (maxval[plane] - minval[plane]) as f32;
166            if spread > largestSpreadSoFar {
167                largestDimension = plane;
168                largestSpreadSoFar = spread;
169            }
170        }
171        retval = largestDimension;
172    }
173    retval as i32
174}
175
176pub fn centerBox(
177    boxStart: i32,
178    boxSize: i32,
179    colorfreqtable: &mut HashMap<i32, Tuple>,
180    depth: i32,
181    newTuple: &mut [i32],
182) {
183    for plane in 0..depth {
184        let mut maxval = colorfreqtable.get(&(boxStart)).unwrap().tuple[plane as usize];
185        let mut minval = maxval;
186
187        for i in 1..boxSize {
188            let v = colorfreqtable.get(&(boxStart + i)).unwrap().tuple[plane as usize];
189            minval = minval.min(v);
190            maxval = maxval.max(v);
191        }
192        newTuple[plane as usize] = (minval + maxval) / 2;
193    }
194}
195
196pub fn averageColors(
197    boxStart: i32,
198    boxSize: i32,
199    colorfreqtable: &mut HashMap<i32, Tuple>,
200    depth: i32,
201    newTuple: &mut [i32],
202) {
203    for plane in 0..depth {
204        let mut sum = 0;
205
206        for i in 0..boxSize {
207            sum += colorfreqtable.get(&(boxStart + i)).unwrap().tuple[plane as usize];
208        }
209
210        newTuple[plane as usize] = sum / boxSize;
211    }
212}
213
214pub fn averagePixels(
215    boxStart: i32,
216    boxSize: i32,
217    colorfreqtable: &mut HashMap<i32, Tuple>,
218    depth: i32,
219    newTuple: &mut [i32],
220) {
221    /* Number of tuples represented by the box */
222    /* Count the tuples in question */
223    let mut n = 0; /* initial value */
224    for i in 0..boxSize {
225        n += colorfreqtable.get(&(boxStart + i)).unwrap().value;
226    }
227
228    for plane in 0..depth {
229        let mut sum = 0;
230        for i in 0..boxSize {
231            sum += colorfreqtable.get(&(boxStart + i)).unwrap().tuple[plane as usize]
232                * colorfreqtable.get(&(boxStart + i)).unwrap().value;
233        }
234        newTuple[plane as usize] = sum / n;
235    }
236}
237
238fn colormapFromBv(
239    newcolors: i32,
240    bv: &[bbox],
241    boxes: i32,
242    colorfreqtable: &mut HashMap<i32, Tuple>,
243    depth: i32,
244    methodForRep: MethodForRep,
245) -> HashMap<i32, Tuple> {
246    /*
247     ** Ok, we've got enough boxes.  Now choose a representative color for
248     ** each box.  There are a number of possible ways to make this choice.
249     ** One would be to choose the center of the box; this ignores any structure
250     ** within the boxes.  Another method would be to average all the colors in
251     ** the box - this is the method specified in Heckbert's paper.  A third
252     ** method is to average all the pixels in the box.
253     */
254    let mut colormap = newColorMap(newcolors, depth);
255
256    for bi in 0..boxes {
257        match methodForRep {
258            MethodForRep::CenterBox => {
259                centerBox(
260                    bv[bi as usize].ind,
261                    bv[bi as usize].colors,
262                    colorfreqtable,
263                    depth,
264                    &mut colormap.get_mut(&bi).unwrap().tuple,
265                );
266            }
267            MethodForRep::AverageColors => {
268                averageColors(
269                    bv[bi as usize].ind,
270                    bv[bi as usize].colors,
271                    colorfreqtable,
272                    depth,
273                    &mut colormap.get_mut(&bi).unwrap().tuple,
274                );
275            }
276            MethodForRep::Auto | MethodForRep::Pixels => {
277                averagePixels(
278                    bv[bi as usize].ind,
279                    bv[bi as usize].colors,
280                    colorfreqtable,
281                    depth,
282                    &mut colormap.get_mut(&bi).unwrap().tuple,
283                );
284            }
285        }
286    }
287    colormap
288}
289
290fn splitBox(
291    bv: &mut [bbox],
292    boxesP: &mut i32,
293    bi: usize,
294    colorfreqtable: &mut HashMap<i32, Tuple>,
295    depth: i32,
296    methodForLargest: MethodForLargest,
297) -> SixelResult<()> {
298    /*----------------------------------------------------------------------------
299       Split Box 'bi' in the box vector bv (so that bv contains one more box
300       than it did as input).  Split it so that each new box represents about
301       half of the pixels in the distribution given by 'colorfreqtable' for
302       the colors in the original box, but with distinct colors in each of the
303       two new boxes.
304
305       Assume the box contains at least two colors.
306    -----------------------------------------------------------------------------*/
307    let boxStart = bv[bi].ind;
308    let boxSize = bv[bi].colors;
309    let sm = bv[bi].sum;
310
311    let max_depth = 16;
312    let mut minval = vec![0; max_depth];
313    let mut maxval = vec![0; max_depth];
314
315    /* assert(max_depth >= depth); */
316
317    findBoxBoundaries(
318        colorfreqtable,
319        depth,
320        boxStart,
321        boxSize,
322        &mut minval,
323        &mut maxval,
324    );
325
326    /* Find the largest dimension, and sort by that component.  I have
327       included two methods for determining the "largest" dimension;
328       first by simply comparing the range in RGB space, and second by
329       transforming into luminosities before the comparison.
330    */
331    let _largestDimension = match methodForLargest {
332        MethodForLargest::Auto | MethodForLargest::Norm => largestByNorm(&minval, &maxval, depth),
333        MethodForLargest::Lum => largestByLuminosity(&minval, &maxval, depth),
334    };
335
336    /* TODO: I think this sort should go after creating a box,
337       not before splitting.  Because you need the sort to use
338       the SIXEL_REP_CENTER_BOX method of choosing a color to
339       represent the final boxes
340    */
341
342    /* Set the gross global variable 'compareplanePlane' as a
343       parameter to compareplane(), which is called by qsort().
344    */
345
346    /* Sholdn't be needed - I use a stupid hasmap - should be refactored.
347    compareplanePlane = largestDimension;
348    qsort((char*) &colorfreqtable.table[boxStart], boxSize,
349          sizeof(colorfreqtable.table[boxStart]),
350          compareplane);*/
351
352    /* Now find the median based on the counts, so that about half
353    the pixels (not colors, pixels) are in each subdivision.  */
354    let mut lowersum = colorfreqtable.get(&boxStart).unwrap().value; /* initial value */
355    let mut i = 1;
356    while i < boxSize - 1 && lowersum < sm / 2 {
357        lowersum += colorfreqtable.get(&(boxStart + i)).unwrap().value;
358        i += 1;
359    }
360    let medianIndex = i;
361    /* Split the box, and sort to bring the biggest boxes to the top.  */
362
363    bv[bi].colors = medianIndex;
364    bv[bi].sum = lowersum;
365    bv[*boxesP as usize].ind = boxStart + medianIndex;
366    bv[*boxesP as usize].colors = boxSize - medianIndex;
367    bv[*boxesP as usize].sum = sm - lowersum;
368    (*boxesP) += 1;
369
370    bv[0..*boxesP as usize].sort_by(sumcompare);
371    Ok(())
372}
373
374pub fn mediancut(
375    colorfreqtable: &mut HashMap<i32, Tuple>,
376    depth: i32,
377    newcolors: i32,
378    methodForLargest: MethodForLargest,
379    methodForRep: MethodForRep,
380    colormapP: &mut HashMap<i32, Tuple>,
381) -> SixelResult<()> {
382    /*----------------------------------------------------------------------------
383       Compute a set of only 'newcolors' colors that best represent an
384       image whose pixels are summarized by the histogram
385       'colorfreqtable'.  Each tuple in that table has depth 'depth'.
386       colorfreqtable.table[i] tells the number of pixels in the subject image
387       have a particular color.
388
389       As a side effect, sort 'colorfreqtable'.
390    -----------------------------------------------------------------------------*/
391    let mut sum = 0;
392
393    for i in 0..colorfreqtable.len() {
394        sum += colorfreqtable.get(&(i as i32)).unwrap().value;
395    }
396
397    /* There is at least one box that contains at least 2 colors; ergo,
398    there is more splitting we can do.  */
399    let mut bv = newBoxVector(colorfreqtable.len() as i32, sum, newcolors);
400    let mut boxes = 1;
401    let mut multicolorBoxesExist = colorfreqtable.len() > 1;
402
403    /* Main loop: split boxes until we have enough. */
404    while boxes < newcolors && multicolorBoxesExist {
405        /* Find the first splittable box. */
406        let mut bi = 0;
407        while bi < boxes && bv[bi as usize].colors < 2 {
408            bi += 1;
409        }
410
411        if bi >= boxes {
412            multicolorBoxesExist = false;
413        } else {
414            splitBox(
415                &mut bv,
416                &mut boxes,
417                bi as usize,
418                colorfreqtable,
419                depth,
420                methodForLargest,
421            )?;
422        }
423    }
424    *colormapP = colormapFromBv(newcolors, &bv, boxes, colorfreqtable, depth, methodForRep);
425
426    Ok(())
427}
428
429pub fn computeHash(data: &[u8], i: usize, depth: i32) -> i32 {
430    let mut hash = 0;
431    for n in 0..depth {
432        hash |= (data[i + depth as usize - 1 - n as usize] as i32 >> 3) << (n * 5);
433    }
434    hash
435}
436
437#[derive(Clone)]
438pub struct Tuple {
439    pub value: i32,
440    pub tuple: Vec<i32>,
441}
442
443pub fn computeHistogram(
444    data: &[u8],
445    length: i32,
446    depth: i32,
447    qualityMode: Quality,
448) -> SixelResult<HashMap<i32, Tuple>> {
449    let (max_sample, mut step) = match qualityMode {
450        Quality::LOW => (18383, length / depth / 18383 * depth),
451        Quality::HIGH => (18383, length / depth / 18383 * depth),
452        Quality::AUTO | Quality::HIGHCOLOR | Quality::FULL => {
453            (4003079, length / depth / 4003079 * depth)
454        }
455    };
456
457    if length < max_sample * depth {
458        step = 6 * depth;
459    }
460
461    if step <= 0 {
462        step = depth;
463    }
464
465    let mut histogram = vec![0; 1 << (depth * 5)];
466
467    let mut memory = vec![0; 1 << (depth * 5)];
468    let mut it = 0;
469    let mut refe = 0;
470    let _refmap = 0;
471
472    let mut i = 0;
473    while i < length {
474        let bucket_index = computeHash(data, i as usize, 3) as usize;
475        if histogram[bucket_index] == 0 {
476            memory[refe] = bucket_index;
477            refe += 1;
478        }
479        if histogram[bucket_index] < (1 << (2 * 8)) - 1 {
480            histogram[bucket_index] += 1;
481        }
482
483        i += step;
484    }
485    let mut colorfreqtable = HashMap::new();
486
487    for i in 0..refe {
488        if histogram[memory[i]] > 0 {
489            let mut tuple: Vec<i32> = vec![0; depth as usize];
490            for n in 0..depth {
491                tuple[(depth - 1 - n) as usize] = ((memory[it] >> (n * 5) & 0x1f) << 3) as i32;
492            }
493            colorfreqtable.insert(
494                i as i32,
495                Tuple {
496                    value: histogram[memory[i]],
497                    tuple,
498                },
499            );
500        }
501        it += 1;
502    }
503    Ok(colorfreqtable)
504}
505
506#[allow(clippy::too_many_arguments)]
507pub fn computeColorMapFromInput(
508    data: &[u8],
509    length: i32,
510    depth: i32,
511    reqColors: i32,
512    methodForLargest: MethodForLargest,
513    methodForRep: MethodForRep,
514    qualityMode: Quality,
515    colormapP: &mut HashMap<i32, Tuple>,
516    origcolors: &mut i32,
517) -> SixelResult<()> {
518    /*----------------------------------------------------------------------------
519       Produce a colormap containing the best colors to represent the
520       image stream in file 'ifP'.  Figure it out using the median cut
521       technique.
522
523       The colormap will have 'reqcolors' or fewer colors in it, unless
524       'allcolors' is true, in which case it will have all the colors that
525       are in the input.
526
527       The colormap has the same maxval as the input.
528
529       Put the colormap in newly allocated storage as a tupletable2
530       and return its address as *colormapP.  Return the number of colors in
531       it as *colorsP and its maxval as *colormapMaxvalP.
532
533       Return the characteristics of the input file as
534       *formatP and *freqPamP.  (This information is not really
535       relevant to our colormap mission; just a fringe benefit).
536    -----------------------------------------------------------------------------*/
537
538    let mut colorfreqtable = computeHistogram(data, length, depth, qualityMode)?;
539    *origcolors = colorfreqtable.len() as i32;
540
541    if colorfreqtable.len() as i32 <= reqColors {
542        /*
543        for i in colorfreqtable.len() as i32..=reqColors {
544            let mut tuple: Vec<i32> = vec![0; depth as usize];
545            for n in 0..depth {
546                tuple[n as usize] = (i * depth) + n;
547            }
548            colorfreqtable.insert(i, Tuple { value: i, tuple });
549        }*/
550
551        for i in 0..colorfreqtable.len() as i32 {
552            colormapP.insert(i, colorfreqtable.get(&i).unwrap().clone());
553        }
554    } else {
555        mediancut(
556            &mut colorfreqtable,
557            depth,
558            reqColors,
559            methodForLargest,
560            methodForRep,
561            colormapP,
562        )?;
563    }
564    Ok(())
565}
566
567/* diffuse error energy to surround pixels */
568pub fn error_diffuse(
569    data: &mut [u8],  /* base address of pixel buffer */
570    pos: i32,         /* address of the destination pixel */
571    depth: i32,       /* color depth in bytes */
572    error: i32,       /* error energy */
573    numerator: i32,   /* numerator of diffusion coefficient */
574    denominator: i32, /* denominator of diffusion coefficient */
575) {
576    let offset = (pos * depth) as usize;
577
578    let mut c = data[offset] as i32 + error * numerator / denominator;
579    if c < 0 {
580        c = 0;
581    }
582    if c >= 1 << 8 {
583        c = (1 << 8) - 1;
584    }
585    data[offset] = c as u8;
586}
587
588pub fn diffuse_none(
589    _data: &mut [u8],
590    _width: i32,
591    _height: i32,
592    _x: i32,
593    _y: i32,
594    _depth: i32,
595    _error: i32,
596) {
597}
598
599pub fn diffuse_fs(
600    data: &mut [u8],
601    width: i32,
602    height: i32,
603    x: i32,
604    y: i32,
605    depth: i32,
606    error: i32,
607) {
608    let pos = y * width + x;
609
610    /* Floyd Steinberg Method
611     *          curr    7/16
612     *  3/16    5/48    1/16
613     */
614    if x < width - 1 && y < height - 1 {
615        /* add error to the right cell */
616        error_diffuse(data, pos + width * 0 + 1, depth, error, 7, 16);
617        /* add error to the left-bottom cell */
618        error_diffuse(data, pos + width * 1 - 1, depth, error, 3, 16);
619        /* add error to the bottom cell */
620        error_diffuse(data, pos + width * 1 + 0, depth, error, 5, 16);
621        /* add error to the right-bottom cell */
622        error_diffuse(data, pos + width * 1 + 1, depth, error, 1, 16);
623    }
624}
625
626pub fn diffuse_atkinson(
627    data: &mut [u8],
628    width: i32,
629    height: i32,
630    x: i32,
631    y: i32,
632    depth: i32,
633    error: i32,
634) {
635    let pos = y * width + x;
636
637    /* Atkinson's Method
638     *          curr    1/8    1/8
639     *   1/8     1/8    1/8
640     *           1/8
641     */
642    if y < height - 2 {
643        /* add error to the right cell */
644        error_diffuse(data, pos + width * 0 + 1, depth, error, 1, 8);
645        /* add error to the 2th right cell */
646        error_diffuse(data, pos + width * 0 + 2, depth, error, 1, 8);
647        /* add error to the left-bottom cell */
648        error_diffuse(data, pos + width * 1 - 1, depth, error, 1, 8);
649        /* add error to the bottom cell */
650        error_diffuse(data, pos + width * 1 + 0, depth, error, 1, 8);
651        /* add error to the right-bottom cell */
652        error_diffuse(data, pos + width * 1 + 1, depth, error, 1, 8);
653        /* add error to the 2th bottom cell */
654        error_diffuse(data, pos + width * 2 + 0, depth, error, 1, 8);
655    }
656}
657
658pub fn diffuse_jajuni(
659    data: &mut [u8],
660    width: i32,
661    height: i32,
662    x: i32,
663    y: i32,
664    depth: i32,
665    error: i32,
666) {
667    let pos = y * width + x;
668
669    /* Jarvis, Judice & Ninke Method
670     *                  curr    7/48    5/48
671     *  3/48    5/48    7/48    5/48    3/48
672     *  1/48    3/48    5/48    3/48    1/48
673     */
674    if pos < (height - 2) * width - 2 {
675        error_diffuse(data, pos + width * 0 + 1, depth, error, 7, 48);
676        error_diffuse(data, pos + width * 0 + 2, depth, error, 5, 48);
677        error_diffuse(data, pos + width * 1 - 2, depth, error, 3, 48);
678        error_diffuse(data, pos + width * 1 - 1, depth, error, 5, 48);
679        error_diffuse(data, pos + width * 1 + 0, depth, error, 7, 48);
680        error_diffuse(data, pos + width * 1 + 1, depth, error, 5, 48);
681        error_diffuse(data, pos + width * 1 + 2, depth, error, 3, 48);
682        error_diffuse(data, pos + width * 2 - 2, depth, error, 1, 48);
683        error_diffuse(data, pos + width * 2 - 1, depth, error, 3, 48);
684        error_diffuse(data, pos + width * 2 + 0, depth, error, 5, 48);
685        error_diffuse(data, pos + width * 2 + 1, depth, error, 3, 48);
686        error_diffuse(data, pos + width * 2 + 2, depth, error, 1, 48);
687    }
688}
689
690pub fn diffuse_stucki(
691    data: &mut [u8],
692    width: i32,
693    height: i32,
694    x: i32,
695    y: i32,
696    depth: i32,
697    error: i32,
698) {
699    let pos = y * width + x;
700
701    /* Stucki's Method
702     *                  curr    8/48    4/48
703     *  2/48    4/48    8/48    4/48    2/48
704     *  1/48    2/48    4/48    2/48    1/48
705     */
706    if pos < (height - 2) * width - 2 {
707        error_diffuse(data, pos + width * 0 + 1, depth, error, 1, 6);
708        error_diffuse(data, pos + width * 0 + 2, depth, error, 1, 12);
709        error_diffuse(data, pos + width * 1 - 2, depth, error, 1, 24);
710        error_diffuse(data, pos + width * 1 - 1, depth, error, 1, 12);
711        error_diffuse(data, pos + width * 1 + 0, depth, error, 1, 6);
712        error_diffuse(data, pos + width * 1 + 1, depth, error, 1, 12);
713        error_diffuse(data, pos + width * 1 + 2, depth, error, 1, 24);
714        error_diffuse(data, pos + width * 2 - 2, depth, error, 1, 48);
715        error_diffuse(data, pos + width * 2 - 1, depth, error, 1, 24);
716        error_diffuse(data, pos + width * 2 + 0, depth, error, 1, 12);
717        error_diffuse(data, pos + width * 2 + 1, depth, error, 1, 24);
718        error_diffuse(data, pos + width * 2 + 2, depth, error, 1, 48);
719    }
720}
721
722pub fn diffuse_burkes(
723    data: &mut [u8],
724    width: i32,
725    height: i32,
726    x: i32,
727    y: i32,
728    depth: i32,
729    error: i32,
730) {
731    let pos = y * width + x;
732
733    /* Burkes' Method
734     *                  curr    4/16    2/16
735     *  1/16    2/16    4/16    2/16    1/16
736     */
737    if pos < (height - 1) * width - 2 {
738        error_diffuse(data, pos + width * 0 + 1, depth, error, 1, 4);
739        error_diffuse(data, pos + width * 0 + 2, depth, error, 1, 8);
740        error_diffuse(data, pos + width * 1 - 2, depth, error, 1, 16);
741        error_diffuse(data, pos + width * 1 - 1, depth, error, 1, 8);
742        error_diffuse(data, pos + width * 1 + 0, depth, error, 1, 4);
743        error_diffuse(data, pos + width * 1 + 1, depth, error, 1, 8);
744        error_diffuse(data, pos + width * 1 + 2, depth, error, 1, 16);
745    }
746}
747
748pub fn mask_a(x: i32, y: i32, c: i32) -> f32 {
749    ((((x + c * 67) + y * 236) * 119) & 255) as f32 / 128.0 - 1.0
750}
751
752pub fn mask_x(x: i32, y: i32, c: i32) -> f32 {
753    ((((x + c * 29) ^ (y * 149)) * 1234) & 511) as f32 / 256.0 - 1.0
754}
755
756use std::collections::HashMap;
757
758use crate::{
759    pixelformat::sixel_helper_compute_depth, MethodForLargest, PixelFormat, Quality, SixelResult,
760};
761use crate::{DiffusionMethod, MethodForRep, SixelError};
762
763/* lookup closest color from palette with "normal" strategy */
764pub fn lookup_normal(
765    pixel: &[u8],
766    depth: i32,
767    palette: &[u8],
768    reqcolor: i32,
769    _cachetable: &mut [u16],
770    complexion: i32,
771) -> i32 {
772    let mut result = -1;
773    let mut diff = i32::MAX;
774
775    /* don't use cachetable in 'normal' strategy */
776
777    for i in 0..reqcolor {
778        let mut distant = 0;
779        let mut r = pixel[0] as i32 - palette[(i * depth + 0) as usize] as i32;
780        distant += r * r * complexion;
781        for n in 1..depth {
782            r = pixel[n as usize] as i32 - palette[(i * depth + n) as usize] as i32;
783            distant += r * r;
784        }
785        if distant < diff {
786            diff = distant;
787            result = i;
788        }
789    }
790
791    result
792}
793
794/* lookup closest color from palette with "fast" strategy */
795pub fn lookup_fast(
796    pixel: &[u8],
797    _depth: i32,
798    palette: &[u8],
799    reqcolor: i32,
800    cachetable: &mut [u16],
801    complexion: i32,
802) -> i32 {
803    let mut result: i32 = -1;
804    let mut diff = i32::MAX;
805    let hash = computeHash(pixel, 0, 3);
806
807    let cache = cachetable[hash as usize];
808    if cache != 0 {
809        /* fast lookup */
810        return cache as i32 - 1;
811    }
812    /* collision */
813    for i in 0..reqcolor {
814        /*          distant = 0;
815         #if 0
816                for (n = 0; n < 3; ++n) {
817                    r = pixel[n] - palette[i * 3 + n];
818                    distant += r * r;
819                }
820        #elif 1*/
821        /* complexion correction */
822        let i = i as usize;
823        let distant = (pixel[0] as i32 - palette[i * 3 + 0] as i32)
824            * (pixel[0] as i32 - palette[i * 3 + 0] as i32)
825            * complexion
826            + (pixel[1] as i32 - palette[i * 3 + 1] as i32)
827                * (pixel[1] as i32 - palette[i * 3 + 1] as i32)
828            + (pixel[2] as i32 - palette[i * 3 + 2] as i32)
829                * (pixel[2] as i32 - palette[i * 3 + 2] as i32);
830        //  #endif
831        if distant < diff {
832            diff = distant;
833            result = i as i32;
834        }
835    }
836    cachetable[hash as usize] = (result + 1) as u16;
837
838    result
839}
840
841pub fn lookup_mono_darkbg(
842    pixel: &[u8],
843    depth: i32,
844    _palette: &[u8],
845    reqcolor: i32,
846    _cachetable: &mut [u16],
847    _complexion: i32,
848) -> i32 {
849    let mut distant = 0;
850    for n in 0..depth {
851        distant += pixel[n as usize] as i32;
852    }
853    if distant >= 128 * reqcolor {
854        1
855    } else {
856        0
857    }
858}
859
860pub fn lookup_mono_lightbg(
861    pixel: &[u8],
862    depth: i32,
863    _palette: &[u8],
864    reqcolor: i32,
865    _cachetable: &mut [u16],
866    _complexion: i32,
867) -> i32 {
868    let mut distant = 0;
869    for n in 0..depth {
870        distant += pixel[n as usize] as i32;
871    }
872    if distant < 128 * reqcolor {
873        1
874    } else {
875        0
876    }
877}
878
879/* choose colors using median-cut method */
880#[allow(clippy::too_many_arguments)]
881pub fn sixel_quant_make_palette(
882    data: &[u8],
883    length: i32,
884    pixelformat: PixelFormat,
885    reqcolors: i32,
886    ncolors: &mut i32,
887    origcolors: &mut i32,
888    methodForLargest: MethodForLargest,
889    methodForRep: MethodForRep,
890    qualityMode: Quality,
891) -> SixelResult<Vec<u8>> {
892    let result_depth = sixel_helper_compute_depth(pixelformat);
893    /*if (result_depth <= 0) {
894        *result = NULL;
895        goto end;
896    }*/
897
898    let depth = result_depth as usize;
899    let mut colormap = HashMap::new();
900    let _ = computeColorMapFromInput(
901        data,
902        length,
903        depth as i32,
904        reqcolors,
905        methodForLargest,
906        methodForRep,
907        qualityMode,
908        &mut colormap,
909        origcolors,
910    );
911    *ncolors = colormap.len() as i32;
912    let mut result = vec![0; colormap.len() * depth as usize];
913    for i in 0..colormap.len() {
914        for n in 0..depth {
915            result[i * depth + n] = colormap.get(&(i as i32)).unwrap().tuple[n] as u8;
916        }
917    }
918    Ok(result)
919}
920
921/* apply color palette into specified pixel buffers */
922#[allow(clippy::too_many_arguments)]
923pub fn sixel_quant_apply_palette(
924    result: &mut [u8],
925    data: &mut [u8],
926    width: i32,
927    height: i32,
928    depth: i32,
929    palette: &mut Vec<u8>,
930    reqcolor: i32,
931    methodForDiffuse: DiffusionMethod,
932    foptimize: bool,
933    foptimize_palette: bool,
934    complexion: i32,
935    cachetable: Option<&mut [u16]>,
936) -> SixelResult<i32> {
937    let mut ncolors: i32;
938    /* check bad reqcolor */
939    if reqcolor < 1 {
940        /*
941                sixel_helper_set_additional_message(
942            "sixel_quant_apply_palette: "
943            "a bad argument is detected, reqcolor < 0.");
944        */
945        return Err(Box::new(SixelError::BadArgument));
946    }
947
948    let mut f_mask = false;
949
950    let f_diffuse = if depth != 3 {
951        diffuse_none
952    } else {
953        match methodForDiffuse {
954            DiffusionMethod::Auto | DiffusionMethod::None => diffuse_none,
955            DiffusionMethod::Atkinson => diffuse_atkinson,
956            DiffusionMethod::FS => diffuse_fs,
957            DiffusionMethod::JaJuNi => diffuse_jajuni,
958            DiffusionMethod::Stucki => diffuse_stucki,
959            DiffusionMethod::Burkes => diffuse_burkes,
960            DiffusionMethod::ADither => {
961                f_mask = true;
962                diffuse_none
963            }
964            DiffusionMethod::XDither => {
965                f_mask = true;
966                diffuse_none
967            }
968        }
969    };
970    type LookupFunc = fn(&[u8], i32, &[u8], i32, &mut [u16], i32) -> i32;
971    let mut f_lookup: Option<LookupFunc> = None;
972    if reqcolor == 2 {
973        let mut sum1 = 0;
974        let mut sum2 = 0;
975        for n in 0..depth {
976            sum1 += palette[n as usize] as i32;
977        }
978        for n in depth..(depth + depth) {
979            sum2 += palette[n as usize] as i32;
980        }
981        if sum1 == 0 && sum2 == 255 * 3 {
982            f_lookup = Some(lookup_mono_darkbg);
983        } else if sum1 == 255 * 3 && sum2 == 0 {
984            f_lookup = Some(lookup_mono_lightbg);
985        }
986    }
987    if f_lookup.is_none() {
988        if foptimize && depth == 3 {
989            f_lookup = Some(lookup_fast);
990        } else {
991            f_lookup = Some(lookup_normal);
992        }
993    }
994
995    let mut cc = vec![0u16, 1 << (depth * 5)];
996    let indextable = match cachetable {
997        Some(table) => table,
998        None => &mut cc,
999    };
1000
1001    if foptimize_palette {
1002        ncolors = 0;
1003        let mut new_palette = vec![0; crate::SIXEL_PALETTE_MAX * depth as usize];
1004        let mut migration_map = vec![0; crate::SIXEL_PALETTE_MAX];
1005
1006        if f_mask {
1007            for y in 0..height {
1008                for x in 0..width {
1009                    let mut copy: Vec<u8> = Vec::new();
1010
1011                    let pos = y * width + x;
1012                    for d in 0..depth {
1013                        let mut val = data[(pos * depth + d) as usize] as i32;
1014                        if matches!(methodForDiffuse, DiffusionMethod::ADither) {
1015                            val += (mask_a(x, y, d) * 32.0) as i32;
1016                        } else {
1017                            val += (mask_x(x, y, d) * 32.0) as i32;
1018                        }
1019                        copy.push(val.clamp(0, 255) as u8);
1020                    }
1021                    //                   &[u8], i32, &[u8], i32, &mut [u16], i32
1022                    let color_index =
1023                        f_lookup.unwrap()(&copy, depth, palette, reqcolor, indextable, complexion)
1024                            as usize;
1025                    if migration_map[color_index] == 0 {
1026                        result[pos as usize] = ncolors as u8;
1027                        for n in 0..depth {
1028                            new_palette[(ncolors * depth + n) as usize] =
1029                                palette[color_index * depth as usize + n as usize];
1030                        }
1031                        ncolors += 1;
1032                        migration_map[color_index] = ncolors;
1033                    } else {
1034                        result[pos as usize] = migration_map[color_index] as u8 - 1;
1035                    }
1036                }
1037            }
1038            *palette = new_palette;
1039        } else {
1040            for y in 0..height {
1041                for x in 0..width {
1042                    let pos = y * width + x;
1043                    let color_index = f_lookup.unwrap()(
1044                        &data[(pos * depth) as usize..],
1045                        depth,
1046                        palette,
1047                        reqcolor,
1048                        indextable,
1049                        complexion,
1050                    ) as usize;
1051                    if migration_map[color_index] == 0 {
1052                        result[pos as usize] = ncolors as u8;
1053                        for n in 0..depth {
1054                            new_palette[(ncolors * depth + n) as usize] =
1055                                palette[color_index * depth as usize + n as usize];
1056                        }
1057                        ncolors += 1;
1058                        migration_map[color_index] = ncolors;
1059                    } else {
1060                        result[pos as usize] = migration_map[color_index] as u8 - 1;
1061                    }
1062                    for n in 0..depth {
1063                        let offset = data[(pos * depth + n) as usize] as i32
1064                            - palette[color_index * depth as usize + n as usize] as i32;
1065                        f_diffuse(&mut data[n as usize..], width, height, x, y, depth, offset);
1066                    }
1067                }
1068            }
1069            *palette = new_palette;
1070        }
1071    } else {
1072        if f_mask {
1073            for y in 0..height {
1074                for x in 0..width {
1075                    let mut copy = Vec::new();
1076                    let pos = y * width + x;
1077                    for d in 0..depth {
1078                        let mut val = data[(pos * depth + d) as usize] as i32;
1079                        if matches!(methodForDiffuse, DiffusionMethod::ADither) {
1080                            val += (mask_a(x, y, d) * 32.0) as i32;
1081                        } else {
1082                            val += (mask_x(x, y, d) * 32.0) as i32;
1083                        }
1084
1085                        copy.push(val.clamp(0, 255) as u8);
1086                    }
1087                    result[pos as usize] = f_lookup.unwrap()(
1088                        &mut copy, depth, palette, reqcolor, indextable, complexion,
1089                    ) as u8;
1090                }
1091            }
1092        } else {
1093            for y in 0..height {
1094                for x in 0..width {
1095                    let pos = y * width + x;
1096                    let color_index = f_lookup.unwrap()(
1097                        &mut data[(pos * depth) as usize..],
1098                        depth,
1099                        palette,
1100                        reqcolor,
1101                        indextable,
1102                        complexion,
1103                    ) as usize;
1104                    result[pos as usize] = color_index as u8;
1105                    for n in 0..depth {
1106                        let offset = data[(pos * depth + n) as usize] as i32
1107                            - palette[color_index * depth as usize + n as usize] as i32;
1108                        f_diffuse(&mut data[n as usize..], width, height, x, y, depth, offset);
1109                    }
1110                }
1111            }
1112        }
1113        ncolors = reqcolor;
1114    }
1115
1116    Ok(ncolors)
1117}
1118
1119/* emacs Local Variables:      */
1120/* emacs mode: c               */
1121/* emacs tab-width: 4          */
1122/* emacs indent-tabs-mode: nil */
1123/* emacs c-basic-offset: 4     */
1124/* emacs End:                  */
1125/* vim: set expandtab ts=4 sts=4 sw=4 : */
1126/* EOF */
1127
1128/*
1129 *
1130 * mediancut algorithm implementation is imported from pnmcolormap.c
1131 * in netpbm library.
1132 * http://netpbm.sourceforge.net/
1133 *
1134 * *******************************************************************************
1135 *                  original license block of pnmcolormap.c
1136 * *******************************************************************************
1137 *
1138 *   Derived from ppmquant, originally by Jef Poskanzer.
1139 *
1140 *   Copyright (C) 1989, 1991 by Jef Poskanzer.
1141 *   Copyright (C) 2001 by Bryan Henderson.
1142 *
1143 *   Permission to use, copy, modify, and distribute this software and its
1144 *   documentation for any purpose and without fee is hereby granted, provided
1145 *   that the above copyright notice appear in all copies and that both that
1146 *   copyright notice and this permission notice appear in supporting
1147 *   documentation.  This software is provided "as is" without express or
1148 *   implied warranty.
1149 *
1150 * ******************************************************************************
1151 *
1152 * Copyright (c) 2014-2018 Hayaki Saito
1153 *
1154 * Permission is hereby granted, free of charge, to any person obtaining a copy of
1155 * this software and associated documentation files (the "Software"), to deal in
1156 * the Software without restriction, including without limitation the rights to
1157 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
1158 * the Software, and to permit persons to whom the Software is furnished to do so,
1159 * subject to the following conditions:
1160 *
1161 * The above copyright notice and this permission notice shall be included in all
1162 * copies or substantial portions of the Software.
1163 *
1164 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1165 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
1166 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
1167 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
1168 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1169 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1170 *
1171 *
1172 */