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