1 //! NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
2 //! See "Kohonen neural networks for optimal colour quantization"
3 //! in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
4 //! for a discussion of the algorithm.
5 //! See also <https://scientificgems.wordpress.com/stuff/neuquant-fast-high-quality-image-quantization/>
6 
7 /* NeuQuant Neural-Net Quantization Algorithm
8  * ------------------------------------------
9  *
10  * Copyright (c) 1994 Anthony Dekker
11  *
12  * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
13  * See "Kohonen neural networks for optimal colour quantization"
14  * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
15  * for a discussion of the algorithm.
16  * See also https://scientificgems.wordpress.com/stuff/neuquant-fast-high-quality-image-quantization/
17  *
18  * Any party obtaining a copy of these files from the author, directly or
19  * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
20  * world-wide, paid up, royalty-free, nonexclusive right and license to deal
21  * in this software and documentation files (the "Software"), including without
22  * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
23  * and/or sell copies of the Software, and to permit persons who receive
24  * copies from any such party to do so, with the only requirement being
25  * that this copyright notice remain intact.
26  *
27  *
28  * Incorporated bugfixes and alpha channel handling from pngnq
29  * http://pngnq.sourceforge.net
30  *
31  */
32 
33 use math::utils::clamp;
34 use std::cmp::{max, min};
35 
36 const CHANNELS: usize = 4;
37 
38 const RADIUS_DEC: i32 = 30; // factor of 1/30 each cycle
39 
40 const ALPHA_BIASSHIFT: i32 = 10; // alpha starts at 1
41 const INIT_ALPHA: i32 = 1 << ALPHA_BIASSHIFT; // biased by 10 bits
42 
43 const GAMMA: f64 = 1024.0;
44 const BETA: f64 = 1.0 / GAMMA;
45 const BETAGAMMA: f64 = BETA * GAMMA;
46 
47 // four primes near 500 - assume no image has a length so large
48 // that it is divisible by all four primes
49 const PRIMES: [usize; 4] = [499, 491, 478, 503];
50 
51 #[derive(Clone, Copy)]
52 struct Quad<T> {
53     r: T,
54     g: T,
55     b: T,
56     a: T,
57 }
58 
59 type Neuron = Quad<f64>;
60 type Color = Quad<i32>;
61 
62 /// Neural network color quantizer
63 pub struct NeuQuant {
64     network: Vec<Neuron>,
65     colormap: Vec<Color>,
66     netindex: Vec<usize>,
67     bias: Vec<f64>, // bias and freq arrays for learning
68     freq: Vec<f64>,
69     samplefac: i32,
70     netsize: usize,
71 }
72 
73 impl NeuQuant {
74     /// Creates a new neural network and trains it with the supplied data
new(samplefac: i32, colors: usize, pixels: &[u8]) -> Self75     pub fn new(samplefac: i32, colors: usize, pixels: &[u8]) -> Self {
76         let netsize = colors;
77         let mut this = NeuQuant {
78             network: Vec::with_capacity(netsize),
79             colormap: Vec::with_capacity(netsize),
80             netindex: vec![0; 256],
81             bias: Vec::with_capacity(netsize),
82             freq: Vec::with_capacity(netsize),
83             samplefac,
84             netsize,
85         };
86         this.init(pixels);
87         this
88     }
89 
90     /// Initializes the neural network and trains it with the supplied data
init(&mut self, pixels: &[u8])91     pub fn init(&mut self, pixels: &[u8]) {
92         self.network.clear();
93         self.colormap.clear();
94         self.bias.clear();
95         self.freq.clear();
96         let freq = (self.netsize as f64).recip();
97         for i in 0..self.netsize {
98             let tmp = (i as f64) * 256.0 / (self.netsize as f64);
99             // Sets alpha values at 0 for dark pixels.
100             let a = if i < 16 { i as f64 * 16.0 } else { 255.0 };
101             self.network.push(Neuron {
102                 r: tmp,
103                 g: tmp,
104                 b: tmp,
105                 a,
106             });
107             self.colormap.push(Color {
108                 r: 0,
109                 g: 0,
110                 b: 0,
111                 a: 255,
112             });
113             self.freq.push(freq);
114             self.bias.push(0.0);
115         }
116         self.learn(pixels);
117         self.build_colormap();
118         self.build_netindex();
119     }
120 
121     /// Maps the pixel in-place to the best-matching color in the color map
122     #[inline(always)]
map_pixel(&self, pixel: &mut [u8])123     pub fn map_pixel(&self, pixel: &mut [u8]) {
124         assert_eq!(pixel.len(), 4);
125         match (pixel[0], pixel[1], pixel[2], pixel[3]) {
126             (r, g, b, a) => {
127                 let i = self.search_netindex(b, g, r, a);
128                 pixel[0] = self.colormap[i].r as u8;
129                 pixel[1] = self.colormap[i].g as u8;
130                 pixel[2] = self.colormap[i].b as u8;
131                 pixel[3] = self.colormap[i].a as u8;
132             }
133         }
134     }
135 
136     /// Finds the best-matching index in the color map for `pixel`
137     #[inline(always)]
index_of(&self, pixel: &[u8]) -> usize138     pub fn index_of(&self, pixel: &[u8]) -> usize {
139         assert_eq!(pixel.len(), 4);
140         match (pixel[0], pixel[1], pixel[2], pixel[3]) {
141             (r, g, b, a) => self.search_netindex(b, g, r, a),
142         }
143     }
144 
145     /// Move neuron i towards biased (a,b,g,r) by factor alpha
alter_single(&mut self, alpha: f64, i: i32, quad: Quad<f64>)146     fn alter_single(&mut self, alpha: f64, i: i32, quad: Quad<f64>) {
147         let n = &mut self.network[i as usize];
148         n.b -= alpha * (n.b - quad.b);
149         n.g -= alpha * (n.g - quad.g);
150         n.r -= alpha * (n.r - quad.r);
151         n.a -= alpha * (n.a - quad.a);
152     }
153 
154     /// Move neuron adjacent neurons towards biased (a,b,g,r) by factor alpha
alter_neighbour(&mut self, alpha: f64, rad: i32, i: i32, quad: Quad<f64>)155     fn alter_neighbour(&mut self, alpha: f64, rad: i32, i: i32, quad: Quad<f64>) {
156         let lo = max(i - rad, 0);
157         let hi = min(i + rad, self.netsize as i32);
158         let mut j = i + 1;
159         let mut k = i - 1;
160         let mut q = 0;
161 
162         while (j < hi) || (k > lo) {
163             let rad_sq = f64::from(rad) * f64::from(rad);
164             let alpha = (alpha * (rad_sq - f64::from(q) * f64::from(q))) / rad_sq;
165             q += 1;
166             if j < hi {
167                 let p = &mut self.network[j as usize];
168                 p.b -= alpha * (p.b - quad.b);
169                 p.g -= alpha * (p.g - quad.g);
170                 p.r -= alpha * (p.r - quad.r);
171                 p.a -= alpha * (p.a - quad.a);
172                 j += 1;
173             }
174             if k > lo {
175                 let p = &mut self.network[k as usize];
176                 p.b -= alpha * (p.b - quad.b);
177                 p.g -= alpha * (p.g - quad.g);
178                 p.r -= alpha * (p.r - quad.r);
179                 p.a -= alpha * (p.a - quad.a);
180                 k -= 1;
181             }
182         }
183     }
184 
185     /// Search for biased BGR values
186     /// finds closest neuron (min dist) and updates freq
187     /// finds best neuron (min dist-bias) and returns position
188     /// for frequently chosen neurons, freq[i] is high and bias[i] is negative
189     /// bias[i] = gamma*((1/self.netsize)-freq[i])
contest(&mut self, b: f64, g: f64, r: f64, a: f64) -> i32190     fn contest(&mut self, b: f64, g: f64, r: f64, a: f64) -> i32 {
191         use std::f64;
192 
193         let mut bestd = f64::MAX;
194         let mut bestbiasd: f64 = bestd;
195         let mut bestpos = -1;
196         let mut bestbiaspos: i32 = bestpos;
197 
198         for i in 0..self.netsize {
199             let bestbiasd_biased = bestbiasd + self.bias[i];
200             let mut dist;
201             let n = &self.network[i];
202             dist = (n.b - b).abs();
203             dist += (n.r - r).abs();
204             if dist < bestd || dist < bestbiasd_biased {
205                 dist += (n.g - g).abs();
206                 dist += (n.a - a).abs();
207                 if dist < bestd {
208                     bestd = dist;
209                     bestpos = i as i32;
210                 }
211                 let biasdist = dist - self.bias[i];
212                 if biasdist < bestbiasd {
213                     bestbiasd = biasdist;
214                     bestbiaspos = i as i32;
215                 }
216             }
217             self.freq[i] -= BETA * self.freq[i];
218             self.bias[i] += BETAGAMMA * self.freq[i];
219         }
220         self.freq[bestpos as usize] += BETA;
221         self.bias[bestpos as usize] -= BETAGAMMA;
222         bestbiaspos
223     }
224 
225     /// Main learning loop
226     /// Note: the number of learning cycles is crucial and the parameters are not
227     /// optimized for net sizes < 26 or > 256. 1064 colors seems to work fine
learn(&mut self, pixels: &[u8])228     fn learn(&mut self, pixels: &[u8]) {
229         let initrad: i32 = self.netsize as i32 / 8; // for 256 cols, radius starts at 32
230         let radiusbiasshift: i32 = 6;
231         let radiusbias: i32 = 1 << radiusbiasshift;
232         let init_bias_radius: i32 = initrad * radiusbias;
233         let mut bias_radius = init_bias_radius;
234         let alphadec = 30 + ((self.samplefac - 1) / 3);
235         let lengthcount = pixels.len() / CHANNELS;
236         let samplepixels = lengthcount / self.samplefac as usize;
237         // learning cycles
238         let n_cycles = match self.netsize >> 1 {
239             n if n <= 100 => 100,
240             n => n,
241         };
242         let delta = match samplepixels / n_cycles {
243             0 => 1,
244             n => n,
245         };
246         let mut alpha = INIT_ALPHA;
247 
248         let mut rad = bias_radius >> radiusbiasshift;
249         if rad <= 1 {
250             rad = 0
251         };
252 
253         let mut pos = 0;
254         let step = *PRIMES
255             .iter()
256             .find(|&&prime| lengthcount % prime != 0)
257             .unwrap_or(&PRIMES[3]);
258 
259         let mut i = 0;
260         while i < samplepixels {
261             let (r, g, b, a) = {
262                 let p = &pixels[CHANNELS * pos..][..CHANNELS];
263                 (
264                     f64::from(p[0]),
265                     f64::from(p[1]),
266                     f64::from(p[2]),
267                     f64::from(p[3]),
268                 )
269             };
270 
271             let j = self.contest(b, g, r, a);
272 
273             let alpha_ = (1.0 * f64::from(alpha)) / f64::from(INIT_ALPHA);
274             self.alter_single(alpha_, j, Quad { b, g, r, a });
275             if rad > 0 {
276                 self.alter_neighbour(alpha_, rad, j, Quad { b, g, r, a })
277             };
278 
279             pos += step;
280             while pos >= lengthcount {
281                 pos -= lengthcount
282             }
283 
284             i += 1;
285             if i % delta == 0 {
286                 alpha -= alpha / alphadec;
287                 bias_radius -= bias_radius / RADIUS_DEC;
288                 rad = bias_radius >> radiusbiasshift;
289                 if rad <= 1 {
290                     rad = 0
291                 };
292             }
293         }
294     }
295 
296     /// initializes the color map
build_colormap(&mut self)297     fn build_colormap(&mut self) {
298         for i in 0usize..self.netsize {
299             self.colormap[i].b = clamp(self.network[i].b.round() as i32, 0, 255);
300             self.colormap[i].g = clamp(self.network[i].g.round() as i32, 0, 255);
301             self.colormap[i].r = clamp(self.network[i].r.round() as i32, 0, 255);
302             self.colormap[i].a = clamp(self.network[i].a.round() as i32, 0, 255);
303         }
304     }
305 
306     /// Insertion sort of network and building of netindex[0..255]
build_netindex(&mut self)307     fn build_netindex(&mut self) {
308         let mut previouscol = 0;
309         let mut startpos = 0;
310 
311         for i in 0..self.netsize {
312             let mut p = self.colormap[i];
313             let mut q;
314             let mut smallpos = i;
315             let mut smallval = p.g as usize; // index on g
316                                              // find smallest in i..netsize-1
317             for j in (i + 1)..self.netsize {
318                 q = self.colormap[j];
319                 if (q.g as usize) < smallval {
320                     // index on g
321                     smallpos = j;
322                     smallval = q.g as usize; // index on g
323                 }
324             }
325             q = self.colormap[smallpos];
326             // swap p (i) and q (smallpos) entries
327             if i != smallpos {
328                 ::std::mem::swap(&mut p, &mut q);
329                 self.colormap[i] = p;
330                 self.colormap[smallpos] = q;
331             }
332             // smallval entry is now in position i
333             if smallval != previouscol {
334                 self.netindex[previouscol] = (startpos + i) >> 1;
335                 for j in (previouscol + 1)..smallval {
336                     self.netindex[j] = i
337                 }
338                 previouscol = smallval;
339                 startpos = i;
340             }
341         }
342         let max_netpos = self.netsize - 1;
343         self.netindex[previouscol] = (startpos + max_netpos) >> 1;
344         for j in (previouscol + 1)..256 {
345             self.netindex[j] = max_netpos
346         } // really 256
347     }
348 
349     /// Search for best matching color
search_netindex(&self, b: u8, g: u8, r: u8, a: u8) -> usize350     fn search_netindex(&self, b: u8, g: u8, r: u8, a: u8) -> usize {
351         let mut bestd = 1 << 30; // ~ 1_000_000
352         let mut best = 0;
353         // start at netindex[g] and work outwards
354         let mut i = self.netindex[g as usize];
355         let mut j = if i > 0 { i - 1 } else { 0 };
356 
357         while (i < self.netsize) || (j > 0) {
358             if i < self.netsize {
359                 let p = self.colormap[i];
360                 let mut e = p.g - i32::from(g);
361                 let mut dist = e * e; // index key
362                 if dist >= bestd {
363                     break;
364                 } else {
365                     e = p.b - i32::from(b);
366                     dist += e * e;
367                     if dist < bestd {
368                         e = p.r - i32::from(r);
369                         dist += e * e;
370                         if dist < bestd {
371                             e = p.a - i32::from(a);
372                             dist += e * e;
373                             if dist < bestd {
374                                 bestd = dist;
375                                 best = i;
376                             }
377                         }
378                     }
379                     i += 1;
380                 }
381             }
382             if j > 0 {
383                 let p = self.colormap[j];
384                 let mut e = p.g - i32::from(g);
385                 let mut dist = e * e; // index key
386                 if dist >= bestd {
387                     break;
388                 } else {
389                     e = p.b - i32::from(b);
390                     dist += e * e;
391                     if dist < bestd {
392                         e = p.r - i32::from(r);
393                         dist += e * e;
394                         if dist < bestd {
395                             e = p.a - i32::from(a);
396                             dist += e * e;
397                             if dist < bestd {
398                                 bestd = dist;
399                                 best = j;
400                             }
401                         }
402                     }
403                     j -= 1;
404                 }
405             }
406         }
407         best
408     }
409 }
410