Skip to main content

maple_render_core/
quantize.rs

1//! Median-cut color quantization with Floyd-Steinberg dithering.
2use image::RgbaImage;
3
4const HIST_C0_BITS: usize = 5; // R
5const HIST_C1_BITS: usize = 6; // G
6const HIST_C2_BITS: usize = 5; // B
7
8const HIST_C0_ELEMS: usize = 1 << HIST_C0_BITS;
9const HIST_C1_ELEMS: usize = 1 << HIST_C1_BITS;
10const HIST_C2_ELEMS: usize = 1 << HIST_C2_BITS;
11
12const C0_SHIFT: usize = 8 - HIST_C0_BITS;
13const C1_SHIFT: usize = 8 - HIST_C1_BITS;
14const C2_SHIFT: usize = 8 - HIST_C2_BITS;
15
16// Distance scale factors (G > R > B perceptual weighting). /shrug
17const C0_SCALE: i32 = 2;
18const C1_SCALE: i32 = 3;
19const C2_SCALE: i32 = 1;
20
21// Box subdivision parameters
22const BOX_C0_LOG: usize = HIST_C0_BITS - 3;
23const BOX_C1_LOG: usize = HIST_C1_BITS - 3;
24const BOX_C2_LOG: usize = HIST_C2_BITS - 3;
25
26const BOX_C0_ELEMS: usize = 1 << BOX_C0_LOG;
27const BOX_C1_ELEMS: usize = 1 << BOX_C1_LOG;
28const BOX_C2_ELEMS: usize = 1 << BOX_C2_LOG;
29
30const BOX_C0_SHIFT: usize = C0_SHIFT + BOX_C0_LOG;
31const BOX_C1_SHIFT: usize = C1_SHIFT + BOX_C1_LOG;
32const BOX_C2_SHIFT: usize = C2_SHIFT + BOX_C2_LOG;
33
34const MAXJSAMPLE: i32 = 255;
35const MAXNUMCOLORS: usize = 256;
36
37#[derive(Clone)]
38pub struct Palette {
39    pub red: [u8; 256],
40    pub green: [u8; 256],
41    pub blue: [u8; 256],
42    pub alpha: [u8; 256],
43    pub colors_total: usize,
44}
45
46impl Default for Palette {
47    fn default() -> Self {
48        Palette {
49            red: [0; 256],
50            green: [0; 256],
51            blue: [0; 256],
52            alpha: [255; 256],
53            colors_total: 0,
54        }
55    }
56}
57
58impl Palette {
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    pub fn get(&self, idx: usize) -> (u8, u8, u8, u8) {
64        (self.red[idx], self.green[idx], self.blue[idx], self.alpha[idx])
65    }
66
67    pub fn set(&mut self, idx: usize, r: u8, g: u8, b: u8) {
68        self.red[idx] = r;
69        self.green[idx] = g;
70        self.blue[idx] = b;
71        self.alpha[idx] = 255;
72    }
73}
74
75#[derive(Clone, Copy, Default)]
76struct ColorBox {
77    c0min: i32,
78    c0max: i32,
79    c1min: i32,
80    c1max: i32,
81    c2min: i32,
82    c2max: i32,
83    volume: i64,
84    colorcount: i64,
85}
86
87pub struct Quantizer {
88    histogram: Box<[[[u16; HIST_C2_ELEMS]; HIST_C1_ELEMS]; HIST_C0_ELEMS]>,
89    fserrors: Vec<i16>,
90    error_limiter: Vec<i32>,
91    on_odd_row: bool,
92    palette: Palette,
93}
94
95impl Quantizer {
96    pub fn new(reference: &RgbaImage) -> Self {
97        let mut q = Quantizer {
98            histogram: Box::new([[[0u16; HIST_C2_ELEMS]; HIST_C1_ELEMS]; HIST_C0_ELEMS]),
99            fserrors: Vec::new(),
100            error_limiter: Vec::new(),
101            on_odd_row: false,
102            palette: Palette::new(),
103        };
104
105        q.init_error_limit();
106
107        let width = reference.width() as usize;
108        q.fserrors = vec![0i16; (width + 2) * 3];
109
110        q.prescan_quantize(reference);
111        q.select_colors(MAXNUMCOLORS);
112        q.zero_histogram();
113
114        q
115    }
116
117    fn init_error_limit(&mut self) {
118        self.error_limiter = vec![0i32; (MAXJSAMPLE * 2 + 1) as usize];
119        let table_offset = MAXJSAMPLE as usize;
120
121        const STEPSIZE: i32 = (MAXJSAMPLE + 1) / 16;
122
123        let mut out: i32 = 0;
124
125        // 1:1 up to +- MAXJSAMPLE/16
126        for inp in 0..STEPSIZE {
127            self.error_limiter[table_offset.wrapping_add(inp as usize)] = out;
128            self.error_limiter[table_offset.wrapping_sub(inp as usize)] = -out;
129            out += 1;
130        }
131
132        // 1:2 up to +- 3*MAXJSAMPLE/16
133        for inp in STEPSIZE..(STEPSIZE * 3) {
134            self.error_limiter[table_offset.wrapping_add(inp as usize)] = out;
135            self.error_limiter[table_offset.wrapping_sub(inp as usize)] = -out;
136            if inp & 1 == 0 {
137                out += 1;
138            }
139        }
140
141        // Clamp the rest
142        for inp in (STEPSIZE * 3)..=MAXJSAMPLE {
143            self.error_limiter[table_offset.wrapping_add(inp as usize)] = out;
144            self.error_limiter[table_offset.wrapping_sub(inp as usize)] = -out;
145        }
146    }
147
148    fn zero_histogram(&mut self) {
149        for c0 in 0..HIST_C0_ELEMS {
150            for c1 in 0..HIST_C1_ELEMS {
151                for c2 in 0..HIST_C2_ELEMS {
152                    self.histogram[c0][c1][c2] = 0;
153                }
154            }
155        }
156    }
157
158    fn prescan_quantize(&mut self, img: &RgbaImage) {
159        for pixel in img.pixels() {
160            let r = (pixel[0] as usize) >> C0_SHIFT;
161            let g = (pixel[1] as usize) >> C1_SHIFT;
162            let b = (pixel[2] as usize) >> C2_SHIFT;
163
164            let cell = &mut self.histogram[r][g][b];
165            if *cell < u16::MAX {
166                *cell += 1;
167            }
168        }
169    }
170
171    fn find_biggest_color_pop(boxlist: &[ColorBox], numboxes: usize) -> Option<usize> {
172        let mut maxc: i64 = 0;
173        let mut which = None;
174
175        for (i, bx) in boxlist[..numboxes].iter().enumerate() {
176            if bx.colorcount > maxc && bx.volume > 0 {
177                which = Some(i);
178                maxc = bx.colorcount;
179            }
180        }
181
182        which
183    }
184
185    fn find_biggest_volume(boxlist: &[ColorBox], numboxes: usize) -> Option<usize> {
186        let mut maxv: i64 = 0;
187        let mut which = None;
188
189        for (i, bx) in boxlist[..numboxes].iter().enumerate() {
190            if bx.volume > maxv {
191                which = Some(i);
192                maxv = bx.volume;
193            }
194        }
195
196        which
197    }
198
199    fn update_box(&self, boxp: &mut ColorBox) {
200        let mut c0min = boxp.c0min;
201        let mut c0max = boxp.c0max;
202        let mut c1min = boxp.c1min;
203        let mut c1max = boxp.c1max;
204        let mut c2min = boxp.c2min;
205        let mut c2max = boxp.c2max;
206
207        if c0max > c0min {
208            'outer: for c0 in c0min..=c0max {
209                for c1 in c1min..=c1max {
210                    for c2 in c2min..=c2max {
211                        if self.histogram[c0 as usize][c1 as usize][c2 as usize] != 0 {
212                            boxp.c0min = c0;
213                            c0min = c0;
214                            break 'outer;
215                        }
216                    }
217                }
218            }
219        }
220
221        if c0max > c0min {
222            'outer: for c0 in (c0min..=c0max).rev() {
223                for c1 in c1min..=c1max {
224                    for c2 in c2min..=c2max {
225                        if self.histogram[c0 as usize][c1 as usize][c2 as usize] != 0 {
226                            boxp.c0max = c0;
227                            c0max = c0;
228                            break 'outer;
229                        }
230                    }
231                }
232            }
233        }
234
235        if c1max > c1min {
236            'outer: for c1 in c1min..=c1max {
237                for c0 in c0min..=c0max {
238                    for c2 in c2min..=c2max {
239                        if self.histogram[c0 as usize][c1 as usize][c2 as usize] != 0 {
240                            boxp.c1min = c1;
241                            c1min = c1;
242                            break 'outer;
243                        }
244                    }
245                }
246            }
247        }
248
249        if c1max > c1min {
250            'outer: for c1 in (c1min..=c1max).rev() {
251                for c0 in c0min..=c0max {
252                    for c2 in c2min..=c2max {
253                        if self.histogram[c0 as usize][c1 as usize][c2 as usize] != 0 {
254                            boxp.c1max = c1;
255                            c1max = c1;
256                            break 'outer;
257                        }
258                    }
259                }
260            }
261        }
262
263        if c2max > c2min {
264            'outer: for c2 in c2min..=c2max {
265                for c0 in c0min..=c0max {
266                    for c1 in c1min..=c1max {
267                        if self.histogram[c0 as usize][c1 as usize][c2 as usize] != 0 {
268                            boxp.c2min = c2;
269                            c2min = c2;
270                            break 'outer;
271                        }
272                    }
273                }
274            }
275        }
276
277        if c2max > c2min {
278            'outer: for c2 in (c2min..=c2max).rev() {
279                for c0 in c0min..=c0max {
280                    for c1 in c1min..=c1max {
281                        if self.histogram[c0 as usize][c1 as usize][c2 as usize] != 0 {
282                            boxp.c2max = c2;
283                            c2max = c2;
284                            break 'outer;
285                        }
286                    }
287                }
288            }
289        }
290
291        let dist0 = ((c0max - c0min) << C0_SHIFT) as i64 * C0_SCALE as i64;
292        let dist1 = ((c1max - c1min) << C1_SHIFT) as i64 * C1_SCALE as i64;
293        let dist2 = ((c2max - c2min) << C2_SHIFT) as i64 * C2_SCALE as i64;
294        boxp.volume = dist0 * dist0 + dist1 * dist1 + dist2 * dist2;
295
296        let mut ccount: i64 = 0;
297        for c0 in c0min..=c0max {
298            for c1 in c1min..=c1max {
299                for c2 in c2min..=c2max {
300                    if self.histogram[c0 as usize][c1 as usize][c2 as usize] != 0 {
301                        ccount += 1;
302                    }
303                }
304            }
305        }
306        boxp.colorcount = ccount;
307    }
308
309    fn median_cut(
310        &self,
311        boxlist: &mut [ColorBox],
312        mut numboxes: usize,
313        desired_colors: usize,
314    ) -> usize {
315        while numboxes < desired_colors {
316            // Select box to split
317            let b1_idx = if numboxes * 2 <= desired_colors {
318                Self::find_biggest_color_pop(boxlist, numboxes)
319            } else {
320                Self::find_biggest_volume(boxlist, numboxes)
321            };
322
323            let b1_idx = match b1_idx {
324                Some(idx) => idx,
325                None => break,
326            };
327
328            let b1 = boxlist[b1_idx];
329            let b2_idx = numboxes;
330            boxlist[b2_idx] = b1;
331
332            let c0 = ((b1.c0max - b1.c0min) << C0_SHIFT) * C0_SCALE;
333            let c1 = ((b1.c1max - b1.c1min) << C1_SHIFT) * C1_SCALE;
334            let c2 = ((b1.c2max - b1.c2min) << C2_SHIFT) * C2_SCALE;
335
336            let mut cmax = c1;
337            let mut n = 1;
338            if c2 > cmax {
339                cmax = c2;
340                n = 2;
341            }
342            if c0 > cmax {
343                n = 0;
344            }
345
346            match n {
347                0 => {
348                    let lb = (b1.c0max + b1.c0min) / 2;
349                    boxlist[b1_idx].c0max = lb;
350                    boxlist[b2_idx].c0min = lb + 1;
351                }
352                1 => {
353                    let lb = (b1.c1max + b1.c1min) / 2;
354                    boxlist[b1_idx].c1max = lb;
355                    boxlist[b2_idx].c1min = lb + 1;
356                }
357                2 => {
358                    let lb = (b1.c2max + b1.c2min) / 2;
359                    boxlist[b1_idx].c2max = lb;
360                    boxlist[b2_idx].c2min = lb + 1;
361                }
362                _ => unreachable!(),
363            }
364
365            self.update_box(&mut boxlist[b1_idx]);
366            self.update_box(&mut boxlist[b2_idx]);
367            numboxes += 1;
368        }
369
370        numboxes
371    }
372
373    fn compute_color(&self, boxp: &ColorBox) -> (u8, u8, u8) {
374        let mut total: i64 = 0;
375        let mut c0total: i64 = 0;
376        let mut c1total: i64 = 0;
377        let mut c2total: i64 = 0;
378
379        for c0 in boxp.c0min..=boxp.c0max {
380            for c1 in boxp.c1min..=boxp.c1max {
381                for c2 in boxp.c2min..=boxp.c2max {
382                    let count = self.histogram[c0 as usize][c1 as usize][c2 as usize] as i64;
383                    if count != 0 {
384                        total += count;
385                        c0total += ((c0 << C0_SHIFT) + (1 << (C0_SHIFT - 1))) as i64 * count;
386                        c1total += ((c1 << C1_SHIFT) + (1 << (C1_SHIFT - 1))) as i64 * count;
387                        c2total += ((c2 << C2_SHIFT) + (1 << (C2_SHIFT - 1))) as i64 * count;
388                    }
389                }
390            }
391        }
392
393        if total > 0 {
394            (
395                ((c0total + (total >> 1)) / total) as u8,
396                ((c1total + (total >> 1)) / total) as u8,
397                ((c2total + (total >> 1)) / total) as u8,
398            )
399        } else {
400            (255, 255, 255)
401        }
402    }
403
404    fn select_colors(&mut self, desired_colors: usize) {
405        let mut boxlist = vec![ColorBox::default(); desired_colors];
406
407        // Initialize one box containing whole space
408        boxlist[0] = ColorBox {
409            c0min: 0,
410            c0max: (MAXJSAMPLE >> C0_SHIFT) as i32,
411            c1min: 0,
412            c1max: (MAXJSAMPLE >> C1_SHIFT) as i32,
413            c2min: 0,
414            c2max: (MAXJSAMPLE >> C2_SHIFT) as i32,
415            volume: 0,
416            colorcount: 0,
417        };
418
419        self.update_box(&mut boxlist[0]);
420        let numboxes = self.median_cut(&mut boxlist, 1, desired_colors);
421
422        for i in 0..numboxes {
423            let (r, g, b) = self.compute_color(&boxlist[i]);
424            self.palette.set(i, r, g, b);
425        }
426        self.palette.colors_total = numboxes;
427    }
428
429    pub fn palette(&self) -> &Palette {
430        &self.palette
431    }
432
433    pub fn palette_mut(&mut self) -> &mut Palette {
434        &mut self.palette
435    }
436
437    fn find_nearby_colors(
438        &self,
439        minc0: i32,
440        minc1: i32,
441        minc2: i32,
442        colorlist: &mut [u8],
443    ) -> usize {
444        let numcolors = self.palette.colors_total;
445
446        let maxc0 = minc0 + ((1 << BOX_C0_SHIFT) - (1 << C0_SHIFT));
447        let centerc0 = (minc0 + maxc0) >> 1;
448        let maxc1 = minc1 + ((1 << BOX_C1_SHIFT) - (1 << C1_SHIFT));
449        let centerc1 = (minc1 + maxc1) >> 1;
450        let maxc2 = minc2 + ((1 << BOX_C2_SHIFT) - (1 << C2_SHIFT));
451        let centerc2 = (minc2 + maxc2) >> 1;
452
453        let mut mindist = vec![0i64; MAXNUMCOLORS];
454        let mut minmaxdist: i64 = i64::MAX;
455
456        for i in 0..numcolors {
457            let x0 = self.palette.red[i] as i32;
458            let (min_dist0, max_dist0) =
459                Self::compute_dist_component(x0, minc0, maxc0, centerc0, C0_SCALE);
460
461            let x1 = self.palette.green[i] as i32;
462            let (min_dist1, max_dist1) =
463                Self::compute_dist_component(x1, minc1, maxc1, centerc1, C1_SCALE);
464
465            let x2 = self.palette.blue[i] as i32;
466            let (min_dist2, max_dist2) =
467                Self::compute_dist_component(x2, minc2, maxc2, centerc2, C2_SCALE);
468
469            mindist[i] = min_dist0 + min_dist1 + min_dist2;
470            let max_dist = max_dist0 + max_dist1 + max_dist2;
471            if max_dist < minmaxdist {
472                minmaxdist = max_dist;
473            }
474        }
475
476        let mut ncolors = 0;
477        for i in 0..numcolors {
478            if mindist[i] <= minmaxdist {
479                colorlist[ncolors] = i as u8;
480                ncolors += 1;
481            }
482        }
483
484        ncolors
485    }
486
487    fn compute_dist_component(
488        x: i32,
489        minc: i32,
490        maxc: i32,
491        centerc: i32,
492        scale: i32,
493    ) -> (i64, i64) {
494        if x < minc {
495            let tdist = (x - minc) as i64 * scale as i64;
496            let min_dist = tdist * tdist;
497            let tdist = (x - maxc) as i64 * scale as i64;
498            let max_dist = tdist * tdist;
499            (min_dist, max_dist)
500        } else if x > maxc {
501            let tdist = (x - maxc) as i64 * scale as i64;
502            let min_dist = tdist * tdist;
503            let tdist = (x - minc) as i64 * scale as i64;
504            let max_dist = tdist * tdist;
505            (min_dist, max_dist)
506        } else {
507            let tdist = if x <= centerc {
508                (x - maxc) as i64 * scale as i64
509            } else {
510                (x - minc) as i64 * scale as i64
511            };
512            (0, tdist * tdist)
513        }
514    }
515
516    fn find_best_colors(
517        &self,
518        minc0: i32,
519        minc1: i32,
520        minc2: i32,
521        numcolors: usize,
522        colorlist: &[u8],
523        bestcolor: &mut [u8],
524    ) {
525        let mut bestdist = vec![i64::MAX; BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS];
526
527        const STEP_C0: i64 = ((1 << C0_SHIFT) * C0_SCALE) as i64;
528        const STEP_C1: i64 = ((1 << C1_SHIFT) * C1_SCALE) as i64;
529        const STEP_C2: i64 = ((1 << C2_SHIFT) * C2_SCALE) as i64;
530
531        for i in 0..numcolors {
532            let icolor = colorlist[i] as usize;
533            let r = self.palette.red[icolor] as i32;
534            let g = self.palette.green[icolor] as i32;
535            let b = self.palette.blue[icolor] as i32;
536
537            let mut inc0 = (minc0 - r) as i64 * C0_SCALE as i64;
538            let mut dist0 = inc0 * inc0;
539            let mut inc1 = (minc1 - g) as i64 * C1_SCALE as i64;
540            dist0 += inc1 * inc1;
541            let mut inc2 = (minc2 - b) as i64 * C2_SCALE as i64;
542            dist0 += inc2 * inc2;
543
544            inc0 = inc0 * (2 * STEP_C0) + STEP_C0 * STEP_C0;
545            inc1 = inc1 * (2 * STEP_C1) + STEP_C1 * STEP_C1;
546            inc2 = inc2 * (2 * STEP_C2) + STEP_C2 * STEP_C2;
547
548            let mut bptr_idx = 0;
549            let mut xx0 = inc0;
550
551            for _ic0 in 0..BOX_C0_ELEMS {
552                let mut dist1 = dist0;
553                let mut xx1 = inc1;
554
555                for _ic1 in 0..BOX_C1_ELEMS {
556                    let mut dist2 = dist1;
557                    let mut xx2 = inc2;
558
559                    for _ic2 in 0..BOX_C2_ELEMS {
560                        if dist2 < bestdist[bptr_idx] {
561                            bestdist[bptr_idx] = dist2;
562                            bestcolor[bptr_idx] = icolor as u8;
563                        }
564                        dist2 += xx2;
565                        xx2 += 2 * STEP_C2 * STEP_C2;
566                        bptr_idx += 1;
567                    }
568                    dist1 += xx1;
569                    xx1 += 2 * STEP_C1 * STEP_C1;
570                }
571                dist0 += xx0;
572                xx0 += 2 * STEP_C0 * STEP_C0;
573            }
574        }
575    }
576
577    fn fill_inverse_cmap(&mut self, c0: i32, c1: i32, c2: i32) {
578        let mut colorlist = vec![0u8; MAXNUMCOLORS];
579        let mut bestcolor = vec![0u8; BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS];
580
581        let bc0 = c0 >> BOX_C0_LOG as i32;
582        let bc1 = c1 >> BOX_C1_LOG as i32;
583        let bc2 = c2 >> BOX_C2_LOG as i32;
584
585        let minc0 = (bc0 << BOX_C0_SHIFT) + (1 << (C0_SHIFT - 1));
586        let minc1 = (bc1 << BOX_C1_SHIFT) + (1 << (C1_SHIFT - 1));
587        let minc2 = (bc2 << BOX_C2_SHIFT) + (1 << (C2_SHIFT - 1));
588
589        let numcolors = self.find_nearby_colors(minc0, minc1, minc2, &mut colorlist);
590        self.find_best_colors(minc0, minc1, minc2, numcolors, &colorlist, &mut bestcolor);
591
592        let base_c0 = (bc0 << BOX_C0_LOG as i32) as usize;
593        let base_c1 = (bc1 << BOX_C1_LOG as i32) as usize;
594        let base_c2 = (bc2 << BOX_C2_LOG as i32) as usize;
595
596        let mut cptr_idx = 0;
597        for ic0 in 0..BOX_C0_ELEMS {
598            for ic1 in 0..BOX_C1_ELEMS {
599                for ic2 in 0..BOX_C2_ELEMS {
600                    self.histogram[base_c0 + ic0][base_c1 + ic1][base_c2 + ic2] =
601                        bestcolor[cptr_idx] as u16 + 1;
602                    cptr_idx += 1;
603                }
604            }
605        }
606    }
607
608    pub fn quantize_no_dither(&mut self, img: &RgbaImage) -> Vec<u8> {
609        let width = img.width() as usize;
610        let height = img.height() as usize;
611        let mut output = vec![0u8; width * height];
612
613        for (y, row) in img.rows().enumerate() {
614            for (x, pixel) in row.enumerate() {
615                let r = pixel[0] as usize;
616                let g = pixel[1] as usize;
617                let b = pixel[2] as usize;
618
619                let c0 = r >> C0_SHIFT;
620                let c1 = g >> C1_SHIFT;
621                let c2 = b >> C2_SHIFT;
622
623                let mut cached = self.histogram[c0][c1][c2];
624                if cached == 0 {
625                    self.fill_inverse_cmap(c0 as i32, c1 as i32, c2 as i32);
626                    cached = self.histogram[c0][c1][c2];
627                }
628
629                output[y * width + x] = (cached - 1) as u8;
630            }
631        }
632
633        output
634    }
635
636    pub fn quantize_fs_dither(&mut self, img: &RgbaImage) -> Vec<u8> {
637        let width = img.width() as usize;
638        let height = img.height() as usize;
639        let mut output = vec![0u8; width * height];
640
641        self.fserrors = vec![0i16; (width + 2) * 3];
642        self.on_odd_row = false;
643
644        let table_offset = MAXJSAMPLE as usize;
645
646        for row in 0..height {
647            let (dir, start_col, end_col, errorptr_start) = if self.on_odd_row {
648                (-1i32, width as i32 - 1, -1i32, (width + 1) * 3)
649            } else {
650                (1i32, 0i32, width as i32, 0usize)
651            };
652
653            let mut cur0: i32 = 0;
654            let mut cur1: i32 = 0;
655            let mut cur2: i32 = 0;
656            let mut belowerr0: i32 = 0;
657            let mut belowerr1: i32 = 0;
658            let mut belowerr2: i32 = 0;
659            let mut bpreverr0: i32 = 0;
660            let mut bpreverr1: i32 = 0;
661            let mut bpreverr2: i32 = 0;
662
663            let mut col = start_col;
664            let mut errorptr = errorptr_start as i32;
665            let dir3 = dir * 3;
666
667            while col != end_col {
668                let x = col as usize;
669                let pixel = img.get_pixel(x as u32, row as u32);
670
671                // Add error from previous and below
672                let ep_idx = (errorptr + dir3) as usize;
673                cur0 = (cur0 + self.fserrors[ep_idx] as i32 + 8) >> 4;
674                cur1 = (cur1 + self.fserrors[ep_idx + 1] as i32 + 8) >> 4;
675                cur2 = (cur2 + self.fserrors[ep_idx + 2] as i32 + 8) >> 4;
676
677                cur0 = self.error_limiter[table_offset.wrapping_add(cur0 as usize)];
678                cur1 = self.error_limiter[table_offset.wrapping_add(cur1 as usize)];
679                cur2 = self.error_limiter[table_offset.wrapping_add(cur2 as usize)];
680
681                cur0 += pixel[0] as i32;
682                cur1 += pixel[1] as i32;
683                cur2 += pixel[2] as i32;
684                cur0 = cur0.clamp(0, 255);
685                cur1 = cur1.clamp(0, 255);
686                cur2 = cur2.clamp(0, 255);
687
688                let c0 = (cur0 as usize) >> C0_SHIFT;
689                let c1 = (cur1 as usize) >> C1_SHIFT;
690                let c2 = (cur2 as usize) >> C2_SHIFT;
691
692                let mut cached = self.histogram[c0][c1][c2];
693                if cached == 0 {
694                    self.fill_inverse_cmap(c0 as i32, c1 as i32, c2 as i32);
695                    cached = self.histogram[c0][c1][c2];
696                }
697
698                let pixcode = (cached - 1) as usize;
699                output[row * width + x] = pixcode as u8;
700
701                cur0 -= self.palette.red[pixcode] as i32;
702                cur1 -= self.palette.green[pixcode] as i32;
703                cur2 -= self.palette.blue[pixcode] as i32;
704
705                let mut bnexterr = cur0;
706                let mut delta = cur0 * 2;
707                cur0 += delta; // 3x
708                self.fserrors[errorptr as usize] = (bpreverr0 + cur0) as i16;
709                cur0 += delta; // 5x
710                bpreverr0 = belowerr0 + cur0;
711                belowerr0 = bnexterr;
712                cur0 += delta; // 7x
713
714                bnexterr = cur1;
715                delta = cur1 * 2;
716                cur1 += delta;
717                self.fserrors[errorptr as usize + 1] = (bpreverr1 + cur1) as i16;
718                cur1 += delta;
719                bpreverr1 = belowerr1 + cur1;
720                belowerr1 = bnexterr;
721                cur1 += delta;
722
723                bnexterr = cur2;
724                delta = cur2 * 2;
725                cur2 += delta;
726                self.fserrors[errorptr as usize + 2] = (bpreverr2 + cur2) as i16;
727                cur2 += delta;
728                bpreverr2 = belowerr2 + cur2;
729                belowerr2 = bnexterr;
730                cur2 += delta;
731
732                col += dir;
733                errorptr += dir3;
734            }
735
736            self.fserrors[errorptr as usize + 1] = bpreverr1 as i16;
737            self.fserrors[errorptr as usize + 2] = bpreverr2 as i16;
738
739            self.on_odd_row = !self.on_odd_row;
740        }
741
742        output
743    }
744
745    pub fn quantize(&mut self, img: &RgbaImage, dither: bool) -> Vec<u8> {
746        if dither { self.quantize_fs_dither(img) } else { self.quantize_no_dither(img) }
747    }
748
749    pub fn sync_palette_from(&mut self, other: &Palette) {
750        self.palette = other.clone();
751    }
752}
753
754pub fn sync_palette(from: &Palette, to: &mut Palette) {
755    *to = from.clone();
756}