UNPKG

gif-encoder-2

Version:
450 lines (382 loc) 11.6 kB
/* NeuQuant Neural-Net Quantization Algorithm * ------------------------------------------ * * Copyright (c) 1994 Anthony Dekker * * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. * See "Kohonen neural networks for optimal colour quantization" * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367. * for a discussion of the algorithm. * See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML * * Any party obtaining a copy of these files from the author, directly or * indirectly, is granted, free of charge, a full and unrestricted irrevocable, * world-wide, paid up, royalty-free, nonexclusive right and license to deal * in this software and documentation files (the "Software"), including without * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons who receive * copies from any such party to do so, with the only requirement being * that this copyright notice remain intact. * * (JavaScript port 2012 by Johan Nordberg) */ var ncycles = 100 // number of learning cycles var netsize = 256 // number of colors used var maxnetpos = netsize - 1 // defs for freq and bias var netbiasshift = 4 // bias for colour values var intbiasshift = 16 // bias for fractions var intbias = 1 << intbiasshift var gammashift = 10 var gamma = 1 << gammashift var betashift = 10 var beta = intbias >> betashift /* beta = 1/1024 */ var betagamma = intbias << (gammashift - betashift) // defs for decreasing radius factor var initrad = netsize >> 3 // for 256 cols, radius starts var radiusbiasshift = 6 // at 32.0 biased by 6 bits var radiusbias = 1 << radiusbiasshift var initradius = initrad * radiusbias //and decreases by a var radiusdec = 30 // factor of 1/30 each cycle // defs for decreasing alpha factor var alphabiasshift = 10 // alpha starts at 1.0 var initalpha = 1 << alphabiasshift var alphadec // biased by 10 bits /* radbias and alpharadbias used for radpower calculation */ var radbiasshift = 8 var radbias = 1 << radbiasshift var alpharadbshift = alphabiasshift + radbiasshift var alpharadbias = 1 << alpharadbshift // four primes near 500 - assume no image has a length so large that it is // divisible by all four primes var prime1 = 499 var prime2 = 491 var prime3 = 487 var prime4 = 503 var minpicturebytes = 3 * prime4 /* Constructor: NeuQuant Arguments: pixels - array of pixels in RGB format samplefac - sampling factor 1 to 30 where lower is better quality > > pixels = [r, g, b, r, g, b, r, g, b, ..] > */ function NeuQuant(pixels, samplefac) { var network // int[netsize][4] var netindex // for network lookup - really 256 // bias and freq arrays for learning var bias var freq var radpower /* Private Method: init sets up arrays */ function init() { network = [] netindex = new Int32Array(256) bias = new Int32Array(netsize) freq = new Int32Array(netsize) radpower = new Int32Array(netsize >> 3) var i, v for (i = 0; i < netsize; i++) { v = (i << (netbiasshift + 8)) / netsize network[i] = new Float64Array([v, v, v, 0]) //network[i] = [v, v, v, 0] freq[i] = intbias / netsize bias[i] = 0 } } /* Private Method: unbiasnet unbiases network to give byte values 0..255 and record position i to prepare for sort */ function unbiasnet() { for (var i = 0; i < netsize; i++) { network[i][0] >>= netbiasshift network[i][1] >>= netbiasshift network[i][2] >>= netbiasshift network[i][3] = i // record color number } } /* Private Method: altersingle moves neuron *i* towards biased (b,g,r) by factor *alpha* */ function altersingle(alpha, i, b, g, r) { network[i][0] -= (alpha * (network[i][0] - b)) / initalpha network[i][1] -= (alpha * (network[i][1] - g)) / initalpha network[i][2] -= (alpha * (network[i][2] - r)) / initalpha } /* Private Method: alterneigh moves neurons in *radius* around index *i* towards biased (b,g,r) by factor *alpha* */ function alterneigh(radius, i, b, g, r) { var lo = Math.abs(i - radius) var hi = Math.min(i + radius, netsize) var j = i + 1 var k = i - 1 var m = 1 var p, a while (j < hi || k > lo) { a = radpower[m++] if (j < hi) { p = network[j++] p[0] -= (a * (p[0] - b)) / alpharadbias p[1] -= (a * (p[1] - g)) / alpharadbias p[2] -= (a * (p[2] - r)) / alpharadbias } if (k > lo) { p = network[k--] p[0] -= (a * (p[0] - b)) / alpharadbias p[1] -= (a * (p[1] - g)) / alpharadbias p[2] -= (a * (p[2] - r)) / alpharadbias } } } /* Private Method: contest searches for biased BGR values */ function contest(b, g, r) { /* finds closest neuron (min dist) and updates freq finds best neuron (min dist-bias) and returns position for frequently chosen neurons, freq[i] is high and bias[i] is negative bias[i] = gamma * ((1 / netsize) - freq[i]) */ var bestd = ~(1 << 31) var bestbiasd = bestd var bestpos = -1 var bestbiaspos = bestpos var i, n, dist, biasdist, betafreq for (i = 0; i < netsize; i++) { n = network[i] dist = Math.abs(n[0] - b) + Math.abs(n[1] - g) + Math.abs(n[2] - r) if (dist < bestd) { bestd = dist bestpos = i } biasdist = dist - (bias[i] >> (intbiasshift - netbiasshift)) if (biasdist < bestbiasd) { bestbiasd = biasdist bestbiaspos = i } betafreq = freq[i] >> betashift freq[i] -= betafreq bias[i] += betafreq << gammashift } freq[bestpos] += beta bias[bestpos] -= betagamma return bestbiaspos } /* Private Method: inxbuild sorts network and builds netindex[0..255] */ function inxbuild() { var i, j, p, q, smallpos, smallval, previouscol = 0, startpos = 0 for (i = 0; i < netsize; i++) { p = network[i] smallpos = i smallval = p[1] // index on g // find smallest in i..netsize-1 for (j = i + 1; j < netsize; j++) { q = network[j] if (q[1] < smallval) { // index on g smallpos = j smallval = q[1] // index on g } } q = network[smallpos] // swap p (i) and q (smallpos) entries if (i != smallpos) { j = q[0] q[0] = p[0] p[0] = j j = q[1] q[1] = p[1] p[1] = j j = q[2] q[2] = p[2] p[2] = j j = q[3] q[3] = p[3] p[3] = j } // smallval entry is now in position i if (smallval != previouscol) { netindex[previouscol] = (startpos + i) >> 1 for (j = previouscol + 1; j < smallval; j++) netindex[j] = i previouscol = smallval startpos = i } } netindex[previouscol] = (startpos + maxnetpos) >> 1 for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos // really 256 } /* Private Method: inxsearch searches for BGR values 0..255 and returns a color index */ function inxsearch(b, g, r) { var a, p, dist var bestd = 1000 // biggest possible dist is 256*3 var best = -1 var i = netindex[g] // index on g var j = i - 1 // start at netindex[g] and work outwards while (i < netsize || j >= 0) { if (i < netsize) { p = network[i] dist = p[1] - g // inx key if (dist >= bestd) i = netsize // stop iter else { i++ if (dist < 0) dist = -dist a = p[0] - b if (a < 0) a = -a dist += a if (dist < bestd) { a = p[2] - r if (a < 0) a = -a dist += a if (dist < bestd) { bestd = dist best = p[3] } } } } if (j >= 0) { p = network[j] dist = g - p[1] // inx key - reverse dif if (dist >= bestd) j = -1 // stop iter else { j-- if (dist < 0) dist = -dist a = p[0] - b if (a < 0) a = -a dist += a if (dist < bestd) { a = p[2] - r if (a < 0) a = -a dist += a if (dist < bestd) { bestd = dist best = p[3] } } } } } return best } /* Private Method: learn "Main Learning Loop" */ function learn() { var i var lengthcount = pixels.length var alphadec = 30 + (samplefac - 1) / 3 var samplepixels = lengthcount / (3 * samplefac) var delta = ~~(samplepixels / ncycles) var alpha = initalpha var radius = initradius var rad = radius >> radiusbiasshift if (rad <= 1) rad = 0 for (i = 0; i < rad; i++) radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad)) var step if (lengthcount < minpicturebytes) { samplefac = 1 step = 3 } else if (lengthcount % prime1 !== 0) { step = 3 * prime1 } else if (lengthcount % prime2 !== 0) { step = 3 * prime2 } else if (lengthcount % prime3 !== 0) { step = 3 * prime3 } else { step = 3 * prime4 } var b, g, r, j var pix = 0 // current pixel i = 0 while (i < samplepixels) { b = (pixels[pix] & 0xff) << netbiasshift g = (pixels[pix + 1] & 0xff) << netbiasshift r = (pixels[pix + 2] & 0xff) << netbiasshift j = contest(b, g, r) altersingle(alpha, j, b, g, r) if (rad !== 0) alterneigh(rad, j, b, g, r) // alter neighbours pix += step if (pix >= lengthcount) pix -= lengthcount i++ if (delta === 0) delta = 1 if (i % delta === 0) { alpha -= alpha / alphadec radius -= radius / radiusdec rad = radius >> radiusbiasshift if (rad <= 1) rad = 0 for (j = 0; j < rad; j++) radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad)) } } } /* Method: buildColormap 1. initializes network 2. trains it 3. removes misconceptions 4. builds colorindex */ function buildColormap() { init() learn() unbiasnet() inxbuild() } this.buildColormap = buildColormap /* Method: getColormap builds colormap from the index returns array in the format: > > [r, g, b, r, g, b, r, g, b, ..] > */ function getColormap() { var map = [] var index = [] for (var i = 0; i < netsize; i++) index[network[i][3]] = i var k = 0 for (var l = 0; l < netsize; l++) { var j = index[l] map[k++] = network[j][0] map[k++] = network[j][1] map[k++] = network[j][2] } return map } this.getColormap = getColormap /* Method: lookupRGB looks for the closest *r*, *g*, *b* color in the map and returns its index */ this.lookupRGB = inxsearch } module.exports = NeuQuant