OLD | NEW |
| (Empty) |
1 /* NeuQuant Neural-Net Quantization Algorithm | |
2 * ------------------------------------------ | |
3 * | |
4 * Copyright (c) 1994 Anthony Dekker | |
5 * | |
6 * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. | |
7 * See "Kohonen neural networks for optimal colour quantization" | |
8 * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367. | |
9 * for a discussion of the algorithm. | |
10 * See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML | |
11 * | |
12 * Any party obtaining a copy of these files from the author, directly or | |
13 * indirectly, is granted, free of charge, a full and unrestricted irrevocable, | |
14 * world-wide, paid up, royalty-free, nonexclusive right and license to deal | |
15 * in this software and documentation files (the "Software"), including without | |
16 * limitation the rights to use, copy, modify, merge, publish, distribute, subli
cense, | |
17 * and/or sell copies of the Software, and to permit persons who receive | |
18 * copies from any such party to do so, with the only requirement being | |
19 * that this copyright notice remain intact. | |
20 * | |
21 * (JavaScript port 2012 by Johan Nordberg) | |
22 */ | |
23 | |
24 var ncycles = 100; // number of learning cycles | |
25 var netsize = 256; // number of colors used | |
26 var maxnetpos = netsize - 1; | |
27 | |
28 // defs for freq and bias | |
29 var netbiasshift = 4; // bias for colour values | |
30 var intbiasshift = 16; // bias for fractions | |
31 var intbias = (1 << intbiasshift); | |
32 var gammashift = 10; | |
33 var gamma = (1 << gammashift); | |
34 var betashift = 10; | |
35 var beta = (intbias >> betashift); /* beta = 1/1024 */ | |
36 var betagamma = (intbias << (gammashift - betashift)); | |
37 | |
38 // defs for decreasing radius factor | |
39 var initrad = (netsize >> 3); // for 256 cols, radius starts | |
40 var radiusbiasshift = 6; // at 32.0 biased by 6 bits | |
41 var radiusbias = (1 << radiusbiasshift); | |
42 var initradius = (initrad * radiusbias); //and decreases by a | |
43 var radiusdec = 30; // factor of 1/30 each cycle | |
44 | |
45 // defs for decreasing alpha factor | |
46 var alphabiasshift = 10; // alpha starts at 1.0 | |
47 var initalpha = (1 << alphabiasshift); | |
48 var alphadec; // biased by 10 bits | |
49 | |
50 /* radbias and alpharadbias used for radpower calculation */ | |
51 var radbiasshift = 8; | |
52 var radbias = (1 << radbiasshift); | |
53 var alpharadbshift = (alphabiasshift + radbiasshift); | |
54 var alpharadbias = (1 << alpharadbshift); | |
55 | |
56 // four primes near 500 - assume no image has a length so large that it is | |
57 // divisible by all four primes | |
58 var prime1 = 499; | |
59 var prime2 = 491; | |
60 var prime3 = 487; | |
61 var prime4 = 503; | |
62 var minpicturebytes = (3 * prime4); | |
63 | |
64 /* | |
65 Constructor: NeuQuant | |
66 | |
67 Arguments: | |
68 | |
69 pixels - array of pixels in RGB format | |
70 samplefac - sampling factor 1 to 30 where lower is better quality | |
71 | |
72 > | |
73 > pixels = [r, g, b, r, g, b, r, g, b, ..] | |
74 > | |
75 */ | |
76 function NeuQuant(pixels, samplefac) { | |
77 var network; // int[netsize][4] | |
78 var netindex; // for network lookup - really 256 | |
79 | |
80 // bias and freq arrays for learning | |
81 var bias; | |
82 var freq; | |
83 var radpower; | |
84 | |
85 /* | |
86 Private Method: init | |
87 | |
88 sets up arrays | |
89 */ | |
90 function init() { | |
91 network = []; | |
92 netindex = new Int32Array(256); | |
93 bias = new Int32Array(netsize); | |
94 freq = new Int32Array(netsize); | |
95 radpower = new Int32Array(netsize >> 3); | |
96 | |
97 var i, v; | |
98 for (i = 0; i < netsize; i++) { | |
99 v = (i << (netbiasshift + 8)) / netsize; | |
100 network[i] = new Float64Array([v, v, v, 0]); | |
101 //network[i] = [v, v, v, 0] | |
102 freq[i] = intbias / netsize; | |
103 bias[i] = 0; | |
104 } | |
105 } | |
106 | |
107 /* | |
108 Private Method: unbiasnet | |
109 | |
110 unbiases network to give byte values 0..255 and record position i to prepare
for sort | |
111 */ | |
112 function unbiasnet() { | |
113 for (var i = 0; i < netsize; i++) { | |
114 network[i][0] >>= netbiasshift; | |
115 network[i][1] >>= netbiasshift; | |
116 network[i][2] >>= netbiasshift; | |
117 network[i][3] = i; // record color number | |
118 } | |
119 } | |
120 | |
121 /* | |
122 Private Method: altersingle | |
123 | |
124 moves neuron *i* towards biased (b,g,r) by factor *alpha* | |
125 */ | |
126 function altersingle(alpha, i, b, g, r) { | |
127 network[i][0] -= (alpha * (network[i][0] - b)) / initalpha; | |
128 network[i][1] -= (alpha * (network[i][1] - g)) / initalpha; | |
129 network[i][2] -= (alpha * (network[i][2] - r)) / initalpha; | |
130 } | |
131 | |
132 /* | |
133 Private Method: alterneigh | |
134 | |
135 moves neurons in *radius* around index *i* towards biased (b,g,r) by factor
*alpha* | |
136 */ | |
137 function alterneigh(radius, i, b, g, r) { | |
138 var lo = Math.abs(i - radius); | |
139 var hi = Math.min(i + radius, netsize); | |
140 | |
141 var j = i + 1; | |
142 var k = i - 1; | |
143 var m = 1; | |
144 | |
145 var p, a; | |
146 while ((j < hi) || (k > lo)) { | |
147 a = radpower[m++]; | |
148 | |
149 if (j < hi) { | |
150 p = network[j++]; | |
151 p[0] -= (a * (p[0] - b)) / alpharadbias; | |
152 p[1] -= (a * (p[1] - g)) / alpharadbias; | |
153 p[2] -= (a * (p[2] - r)) / alpharadbias; | |
154 } | |
155 | |
156 if (k > lo) { | |
157 p = network[k--]; | |
158 p[0] -= (a * (p[0] - b)) / alpharadbias; | |
159 p[1] -= (a * (p[1] - g)) / alpharadbias; | |
160 p[2] -= (a * (p[2] - r)) / alpharadbias; | |
161 } | |
162 } | |
163 } | |
164 | |
165 /* | |
166 Private Method: contest | |
167 | |
168 searches for biased BGR values | |
169 */ | |
170 function contest(b, g, r) { | |
171 /* | |
172 finds closest neuron (min dist) and updates freq | |
173 finds best neuron (min dist-bias) and returns position | |
174 for frequently chosen neurons, freq[i] is high and bias[i] is negative | |
175 bias[i] = gamma * ((1 / netsize) - freq[i]) | |
176 */ | |
177 | |
178 var bestd = ~(1 << 31); | |
179 var bestbiasd = bestd; | |
180 var bestpos = -1; | |
181 var bestbiaspos = bestpos; | |
182 | |
183 var i, n, dist, biasdist, betafreq; | |
184 for (i = 0; i < netsize; i++) { | |
185 n = network[i]; | |
186 | |
187 dist = Math.abs(n[0] - b) + Math.abs(n[1] - g) + Math.abs(n[2] - r); | |
188 if (dist < bestd) { | |
189 bestd = dist; | |
190 bestpos = i; | |
191 } | |
192 | |
193 biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); | |
194 if (biasdist < bestbiasd) { | |
195 bestbiasd = biasdist; | |
196 bestbiaspos = i; | |
197 } | |
198 | |
199 betafreq = (freq[i] >> betashift); | |
200 freq[i] -= betafreq; | |
201 bias[i] += (betafreq << gammashift); | |
202 } | |
203 | |
204 freq[bestpos] += beta; | |
205 bias[bestpos] -= betagamma; | |
206 | |
207 return bestbiaspos; | |
208 } | |
209 | |
210 /* | |
211 Private Method: inxbuild | |
212 | |
213 sorts network and builds netindex[0..255] | |
214 */ | |
215 function inxbuild() { | |
216 var i, j, p, q, smallpos, smallval, previouscol = 0, startpos = 0; | |
217 for (i = 0; i < netsize; i++) { | |
218 p = network[i]; | |
219 smallpos = i; | |
220 smallval = p[1]; // index on g | |
221 // find smallest in i..netsize-1 | |
222 for (j = i + 1; j < netsize; j++) { | |
223 q = network[j]; | |
224 if (q[1] < smallval) { // index on g | |
225 smallpos = j; | |
226 smallval = q[1]; // index on g | |
227 } | |
228 } | |
229 q = network[smallpos]; | |
230 // swap p (i) and q (smallpos) entries | |
231 if (i != smallpos) { | |
232 j = q[0]; q[0] = p[0]; p[0] = j; | |
233 j = q[1]; q[1] = p[1]; p[1] = j; | |
234 j = q[2]; q[2] = p[2]; p[2] = j; | |
235 j = q[3]; q[3] = p[3]; p[3] = j; | |
236 } | |
237 // smallval entry is now in position i | |
238 | |
239 if (smallval != previouscol) { | |
240 netindex[previouscol] = (startpos + i) >> 1; | |
241 for (j = previouscol + 1; j < smallval; j++) | |
242 netindex[j] = i; | |
243 previouscol = smallval; | |
244 startpos = i; | |
245 } | |
246 } | |
247 netindex[previouscol] = (startpos + maxnetpos) >> 1; | |
248 for (j = previouscol + 1; j < 256; j++) | |
249 netindex[j] = maxnetpos; // really 256 | |
250 } | |
251 | |
252 /* | |
253 Private Method: inxsearch | |
254 | |
255 searches for BGR values 0..255 and returns a color index | |
256 */ | |
257 function inxsearch(b, g, r) { | |
258 var a, p, dist; | |
259 | |
260 var bestd = 1000; // biggest possible dist is 256*3 | |
261 var best = -1; | |
262 | |
263 var i = netindex[g]; // index on g | |
264 var j = i - 1; // start at netindex[g] and work outwards | |
265 | |
266 while ((i < netsize) || (j >= 0)) { | |
267 if (i < netsize) { | |
268 p = network[i]; | |
269 dist = p[1] - g; // inx key | |
270 if (dist >= bestd) i = netsize; // stop iter | |
271 else { | |
272 i++; | |
273 if (dist < 0) dist = -dist; | |
274 a = p[0] - b; if (a < 0) a = -a; | |
275 dist += a; | |
276 if (dist < bestd) { | |
277 a = p[2] - r; if (a < 0) a = -a; | |
278 dist += a; | |
279 if (dist < bestd) { | |
280 bestd = dist; | |
281 best = p[3]; | |
282 } | |
283 } | |
284 } | |
285 } | |
286 if (j >= 0) { | |
287 p = network[j]; | |
288 dist = g - p[1]; // inx key - reverse dif | |
289 if (dist >= bestd) j = -1; // stop iter | |
290 else { | |
291 j--; | |
292 if (dist < 0) dist = -dist; | |
293 a = p[0] - b; if (a < 0) a = -a; | |
294 dist += a; | |
295 if (dist < bestd) { | |
296 a = p[2] - r; if (a < 0) a = -a; | |
297 dist += a; | |
298 if (dist < bestd) { | |
299 bestd = dist; | |
300 best = p[3]; | |
301 } | |
302 } | |
303 } | |
304 } | |
305 } | |
306 | |
307 return best; | |
308 } | |
309 | |
310 /* | |
311 Private Method: learn | |
312 | |
313 "Main Learning Loop" | |
314 */ | |
315 function learn() { | |
316 var i; | |
317 | |
318 var lengthcount = pixels.length; | |
319 var alphadec = 30 + ((samplefac - 1) / 3); | |
320 var samplepixels = lengthcount / (3 * samplefac); | |
321 var delta = ~~(samplepixels / ncycles); | |
322 var alpha = initalpha; | |
323 var radius = initradius; | |
324 | |
325 var rad = radius >> radiusbiasshift; | |
326 | |
327 if (rad <= 1) rad = 0; | |
328 for (i = 0; i < rad; i++) | |
329 radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); | |
330 | |
331 var step; | |
332 if (lengthcount < minpicturebytes) { | |
333 samplefac = 1; | |
334 step = 3; | |
335 } else if ((lengthcount % prime1) !== 0) { | |
336 step = 3 * prime1; | |
337 } else if ((lengthcount % prime2) !== 0) { | |
338 step = 3 * prime2; | |
339 } else if ((lengthcount % prime3) !== 0) { | |
340 step = 3 * prime3; | |
341 } else { | |
342 step = 3 * prime4; | |
343 } | |
344 | |
345 var b, g, r, j; | |
346 var pix = 0; // current pixel | |
347 | |
348 i = 0; | |
349 while (i < samplepixels) { | |
350 b = (pixels[pix] & 0xff) << netbiasshift; | |
351 g = (pixels[pix + 1] & 0xff) << netbiasshift; | |
352 r = (pixels[pix + 2] & 0xff) << netbiasshift; | |
353 | |
354 j = contest(b, g, r); | |
355 | |
356 altersingle(alpha, j, b, g, r); | |
357 if (rad !== 0) alterneigh(rad, j, b, g, r); // alter neighbours | |
358 | |
359 pix += step; | |
360 if (pix >= lengthcount) pix -= lengthcount; | |
361 | |
362 i++; | |
363 | |
364 if (delta === 0) delta = 1; | |
365 if (i % delta === 0) { | |
366 alpha -= alpha / alphadec; | |
367 radius -= radius / radiusdec; | |
368 rad = radius >> radiusbiasshift; | |
369 | |
370 if (rad <= 1) rad = 0; | |
371 for (j = 0; j < rad; j++) | |
372 radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); | |
373 } | |
374 } | |
375 } | |
376 | |
377 /* | |
378 Method: buildColormap | |
379 | |
380 1. initializes network | |
381 2. trains it | |
382 3. removes misconceptions | |
383 4. builds colorindex | |
384 */ | |
385 function buildColormap() { | |
386 init(); | |
387 learn(); | |
388 unbiasnet(); | |
389 inxbuild(); | |
390 } | |
391 this.buildColormap = buildColormap; | |
392 | |
393 /* | |
394 Method: getColormap | |
395 | |
396 builds colormap from the index | |
397 | |
398 returns array in the format: | |
399 | |
400 > | |
401 > [r, g, b, r, g, b, r, g, b, ..] | |
402 > | |
403 */ | |
404 function getColormap() { | |
405 var map = []; | |
406 var index = []; | |
407 | |
408 for (var i = 0; i < netsize; i++) | |
409 index[network[i][3]] = i; | |
410 | |
411 var k = 0; | |
412 for (var l = 0; l < netsize; l++) { | |
413 var j = index[l]; | |
414 map[k++] = (network[j][0]); | |
415 map[k++] = (network[j][1]); | |
416 map[k++] = (network[j][2]); | |
417 } | |
418 return map; | |
419 } | |
420 this.getColormap = getColormap; | |
421 | |
422 /* | |
423 Method: lookupRGB | |
424 | |
425 looks for the closest *r*, *g*, *b* color in the map and | |
426 returns its index | |
427 */ | |
428 this.lookupRGB = inxsearch; | |
429 } | |
430 | |
431 module.exports = NeuQuant; | |
OLD | NEW |