View Javadoc

1   package org.freehep.graphicsio.gif;
2   
3   /* NeuQuant Neural-Net Quantization Algorithm
4    * ------------------------------------------
5    *
6    * Copyright (c) 1994 Anthony Dekker
7    *
8    * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
9    * See "Kohonen neural networks for optimal colour quantization"
10   * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
11   * for a discussion of the algorithm.
12   * See also  http://www.acm.org/~dekker/NEUQUANT.HTML
13   *
14   * Any party obtaining a copy of these files from the author, directly or
15   * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
16   * world-wide, paid up, royalty-free, nonexclusive right and license to deal
17   * in this software and documentation files (the "Software"), including without
18   * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
19   * and/or sell copies of the Software, and to permit persons who receive
20   * copies from any such party to do so, with the only requirement being
21   * that this copyright notice remain intact.
22   */
23  
24  public class NeuQuant {
25  
26      public static final int ncycles	=	100;			// no. of learning cycles
27  
28      public static final int netsize  = 255;		// number of colours used
29      public static final int specials  = 3;		// number of reserved colours used
30      public static final int bgColour  = specials-1;	// reserved background colour
31      public static final int cutnetsize  = netsize - specials;
32      public static final int maxnetpos  = netsize-1;
33  
34      public static final int initrad	 = netsize/8;   // for 256 cols, radius starts at 32
35      public static final int radiusbiasshift = 6;
36      public static final int radiusbias = 1 << radiusbiasshift;
37      public static final int initBiasRadius = initrad*radiusbias;
38      public static final int radiusdec = 30; // factor of 1/30 each cycle
39  
40      public static final int alphabiasshift = 10;			// alpha starts at 1
41      public static final int initalpha      = 1<<alphabiasshift; // biased by 10 bits
42  
43      public static final double gamma = 1024.0;
44      public static final double beta = 1.0/1024.0;
45      public static final double betagamma = beta * gamma;
46      
47      private double [] [] network = new double [netsize] [3]; // the network itself
48      protected int [] [] colormap = new int [netsize] [4]; // the network itself
49      
50      private int [] netindex = new int [256]; // for network lookup - really 256
51      
52      private double [] bias = new double [netsize];  // bias and freq arrays for learning
53      private double [] freq = new double [netsize];
54  
55      // four primes near 500 - assume no image has a length so large
56      // that it is divisible by all four primes
57     
58      public static final int prime1	=	499;
59      public static final int prime2	=	491;
60      public static final int prime3	=	487;
61      public static final int prime4	=	503;
62      public static final int maxprime=	prime4;
63      
64      protected int [][] pixels = null;
65      private int samplefac = 0;
66  
67  
68      public NeuQuant (int sample, int[][] pixels) {
69          if (sample < 1) throw new RuntimeException ("Sample must be 1..30");
70          if (sample > 30) throw new RuntimeException ("Sample must be 1..30");
71          samplefac = sample;
72          setPixels (pixels);
73  		setUpArrays ();
74      }
75          
76      public int getColorCount () {
77      	return netsize;
78      }
79  
80      public int[] getColorMap() {
81      	// keep entry 0 free for transparent color
82      	int[] c = new int[netsize+1];
83      	c[0] = 0x00000000;
84      	for (int i=0; i<netsize; i++) {
85  		    c[i+1] = (colormap[i][0]     )  |
86  	    	         (colormap[i][1] << 8)  |
87  	    	         (colormap[i][2] << 16) |
88  	    	         (0xFF           << 24);
89      	}
90      	return c;
91      }
92  
93      protected void setUpArrays () {
94      	network [0] [0] = 0.0;	// black
95      	network [0] [1] = 0.0;
96      	network [0] [2] = 0.0;
97      	
98      	network [1] [0] = 1.0;	// white
99      	network [1] [1] = 1.0;
100     	network [1] [2] = 1.0;
101 
102     	// RESERVED bgColour	// background
103     	
104         for (int i=0; i<specials; i++) {
105 		    freq[i] = 1.0 / netsize;
106 		    bias[i] = 0.0;
107         }
108         
109         for (int i=specials; i<netsize; i++) {
110 		    double [] p = network [i];
111 		    p[0] = (256.0 * (i-specials)) / cutnetsize;
112 		    p[1] = (256.0 * (i-specials)) / cutnetsize;
113 		    p[2] = (256.0 * (i-specials)) / cutnetsize;
114 
115 		    freq[i] = 1.0 / netsize;
116 		    bias[i] = 0.0;
117         }
118     }    	
119     
120     private void setPixels (int[][] pixels) {
121         if (pixels.length*pixels[0].length < maxprime) throw new RuntimeException ("Image is too small");
122         this.pixels = pixels;
123     }
124     
125     public void init () {
126         learn ();
127         fix ();
128         inxbuild ();
129     }
130 
131     private void altersingle(double alpha, int i, double b, double g, double r) {
132         // Move neuron i towards biased (b,g,r) by factor alpha
133         double [] n = network[i];				// alter hit neuron
134     	n[0] -= (alpha*(n[0] - b));
135     	n[1] -= (alpha*(n[1] - g));
136     	n[2] -= (alpha*(n[2] - r));
137     }
138 
139     private void alterneigh(double alpha, int rad, int i, double b, double g, double r) {
140         
141     	int lo = i-rad;   if (lo<specials-1) lo=specials-1;
142     	int hi = i+rad;   if (hi>netsize) hi=netsize;
143 
144     	int j = i+1;
145     	int k = i-1;
146     	int q = 0;
147     	while ((j<hi) || (k>lo)) {
148     	    double a = (alpha * (rad*rad - q*q)) / (rad*rad);
149     	    q ++;
150     		if (j<hi) {
151     			double [] p = network[j];
152     			p[0] -= (a*(p[0] - b));
153     			p[1] -= (a*(p[1] - g));
154     			p[2] -= (a*(p[2] - r));
155     			j++;
156     		}
157     		if (k>lo) {
158     			double [] p = network[k];
159     			p[0] -= (a*(p[0] - b));
160     			p[1] -= (a*(p[1] - g));
161     			p[2] -= (a*(p[2] - r));
162     			k--;
163     		}
164     	}
165     }
166     
167     private int contest (double b, double g, double r) {    // Search for biased BGR values
168     	// finds closest neuron (min dist) and updates freq 
169     	// finds best neuron (min dist-bias) and returns position 
170     	// for frequently chosen neurons, freq[i] is high and bias[i] is negative 
171     	// bias[i] = gamma*((1/netsize)-freq[i]) 
172 
173     	double bestd = Float.MAX_VALUE;
174     	double bestbiasd = bestd;
175     	int bestpos = -1;
176     	int bestbiaspos = bestpos;
177         
178     	for (int i=specials; i<netsize; i++) {
179     		double [] n = network[i];
180     		double dist = n[0] - b;   if (dist<0) dist = -dist;
181     		double a = n[1] - g;   if (a<0) a = -a;
182     		dist += a;
183     		a = n[2] - r;   if (a<0) a = -a;
184     		dist += a;
185     		if (dist<bestd) {bestd=dist; bestpos=i;}
186     		double biasdist = dist - bias [i];
187     		if (biasdist<bestbiasd) {bestbiasd=biasdist; bestbiaspos=i;}
188     		freq [i] -= beta * freq [i];
189     		bias [i] += betagamma * freq [i];
190     	}
191     	freq[bestpos] += beta;
192     	bias[bestpos] -= betagamma;
193     	return bestbiaspos;
194     }
195     
196     private int specialFind (double b, double g, double r) {
197     	for (int i=0; i<specials; i++) {
198     		double [] n = network[i];
199     		if (n[0] == b && n[1] == g && n[2] == r) return i;
200     	}
201     	return -1;
202     }
203     
204     private void learn() {
205         int biasRadius = initBiasRadius;
206     	int alphadec = 30 + ((samplefac-1)/3);
207     	int lengthcount = pixels.length*pixels[0].length;
208     	int samplepixels = lengthcount / samplefac;
209     	int delta = samplepixels / ncycles;
210     	int alpha = initalpha;
211 
212 	    int i = 0;
213     	int rad = biasRadius >> radiusbiasshift;
214     	if (rad <= 1) rad = 0;
215 	
216 //    	System.err.println("beginning 1D learning: samplepixels=" + samplepixels + "  rad=" + rad);
217 
218         int step = 0;
219         int pos = 0;
220         
221     	if ((lengthcount%prime1) != 0) step = prime1;
222     	else {
223     		if ((lengthcount%prime2) !=0) step = prime2;
224     		else {
225     			if ((lengthcount%prime3) !=0) step = prime3;
226     			else step = prime4;
227     		}
228     	}
229 	
230     	i = 0;
231     	while (i < samplepixels) {
232     	    int p = pixels [pos / pixels[0].length][pos % pixels[0].length];
233 	        int red   = (p >> 16) & 0xff;
234 	        int green = (p >>  8) & 0xff;
235 	        int blue  = (p      ) & 0xff;
236 
237     		double b = blue;
238     		double g = green;
239     		double r = red;
240 
241     		if (i == 0) {   // remember background colour
242     			network [bgColour] [0] = b;
243     			network [bgColour] [1] = g;
244     			network [bgColour] [2] = r;
245     		}
246 
247     		int j = specialFind (b, g, r);
248     		j = j < 0 ? contest (b, g, r) : j;
249 
250 			if (j >= specials) {   // don't learn for specials
251             	double a = (1.0 * alpha) / initalpha;
252     			altersingle (a, j, b, g, r);
253     			if (rad > 0) alterneigh (a, rad, j, b, g, r);   // alter neighbours
254     		}
255 
256     		pos += step;
257     		while (pos >= lengthcount) pos -= lengthcount;
258 	        
259     		i++;
260     		if (i%delta == 0) {	
261     			alpha -= alpha / alphadec;
262     			biasRadius -= biasRadius / radiusdec;
263     			rad = biasRadius >> radiusbiasshift;
264     			if (rad <= 1) rad = 0;
265     		}
266     	}
267 //    	System.err.println("finished 1D learning: final alpha=" + (1.0 * alpha)/initalpha + "!");
268     }
269 
270     private void fix() {
271         for (int i=0; i<netsize; i++) {
272             for (int j=0; j<3; j++) {
273                 int x = (int) (0.5 + network[i][j]);
274                 if (x < 0) x = 0;
275                 if (x > 255) x = 255;
276                 colormap[i][j] = x;
277             }
278             colormap[i][3] = i;
279         }
280     }
281 
282     private void inxbuild() {
283         // Insertion sort of network and building of netindex[0..255]
284 
285     	int previouscol = 0;
286     	int startpos = 0;
287 
288     	for (int i=0; i<netsize; i++) {
289     		int[] p = colormap[i];
290     		int[] q = null;
291     		int smallpos = i;
292     		int smallval = p[1];			// index on g
293     		// find smallest in i..netsize-1
294     		for (int j=i+1; j<netsize; j++) {
295     			q = colormap[j];
296     			if (q[1] < smallval) {		// index on g
297     				smallpos = j;
298     				smallval = q[1];	// index on g
299     			}
300     		}
301     		q = colormap[smallpos];
302     		// swap p (i) and q (smallpos) entries
303     		if (i != smallpos) {
304     			int j = q[0];   q[0] = p[0];   p[0] = j;
305     			j = q[1];   q[1] = p[1];   p[1] = j;
306     			j = q[2];   q[2] = p[2];   p[2] = j;
307     			j = q[3];   q[3] = p[3];   p[3] = j;
308     		}
309     		// smallval entry is now in position i
310     		if (smallval != previouscol) {
311     			netindex[previouscol] = (startpos+i)>>1;
312     			for (int j=previouscol+1; j<smallval; j++) netindex[j] = i;
313     			previouscol = smallval;
314     			startpos = i;
315     		}
316     	}
317     	netindex[previouscol] = (startpos+maxnetpos)>>1;
318     	for (int j=previouscol+1; j<256; j++) netindex[j] = maxnetpos; // really 256
319     }
320 
321     public int convert (int pixel) {
322         int alfa = (pixel >> 24) & 0xff;
323 	    int r   = (pixel >> 16) & 0xff;
324 	    int g = (pixel >>  8) & 0xff;
325 	    int b  = (pixel      ) & 0xff;
326 	    int i = inxsearch(b, g, r);
327 	    int bb = colormap[i][0];
328 	    int gg = colormap[i][1];
329 	    int rr = colormap[i][2];
330 	    return (alfa << 24) | (rr << 16) | (gg << 8) | (bb);
331     }
332 
333     public int lookup (int pixel) {
334 	    int r = (pixel >> 16) & 0xff;
335 	    int g = (pixel >>  8) & 0xff;
336 	    int b = (pixel      ) & 0xff;
337 	    int i = inxsearch(b, g, r);
338 	    // compensate for transparent color
339 	    return i+1;
340     }
341 
342     private int not_used_slow_inxsearch(int b, int g, int r) {
343         // Search for BGR values 0..255 and return colour index
344     	int bestd = 1000;		// biggest possible dist is 256*3
345     	int best = -1;
346     	for (int i = 0; i<netsize; i++) {
347  			int [] p = colormap[i];
348 			int dist = p[1] - g;
349 			if (dist<0) dist = -dist;
350 			int a = p[0] - b;   if (a<0) a = -a;
351 			dist += a;
352 			a = p[2] - r;   if (a<0) a = -a;
353 			dist += a;
354 			if (dist<bestd) {bestd=dist; best=i;}
355 		}
356 		return best;
357     }
358     
359     protected int inxsearch(int b, int g, int r) {
360         // Search for BGR values 0..255 and return colour index
361     	int bestd = 1000;		// biggest possible dist is 256*3
362     	int best = -1;
363     	int i = netindex[g];	// index on g
364     	int j = i-1;		// start at netindex[g] and work outwards
365 
366     	while ((i<netsize) || (j>=0)) {
367     		if (i<netsize) {
368     			int [] p = colormap[i];
369     			int dist = p[1] - g;		// inx key
370     			if (dist >= bestd) i = netsize;	// stop iter
371     			else {
372     				if (dist<0) dist = -dist;
373     				int a = p[0] - b;   if (a<0) a = -a;
374     				dist += a;
375     				if (dist<bestd) {
376     					a = p[2] - r;   if (a<0) a = -a;
377     					dist += a;
378     					if (dist<bestd) {bestd=dist; best=i;}
379     				}
380     				i++;
381     			}
382     		}
383     		if (j>=0) {
384     			int [] p = colormap[j];
385     			int dist = g - p[1]; // inx key - reverse dif
386     			if (dist >= bestd) j = -1; // stop iter
387     			else {
388     				if (dist<0) dist = -dist;
389     				int a = p[0] - b;   if (a<0) a = -a;
390     				dist += a;
391     				if (dist<bestd) {
392     					a = p[2] - r;   if (a<0) a = -a;
393     					dist += a;
394     					if (dist<bestd) {bestd=dist; best=j;}
395     				}
396     				j--;
397     			}
398     		}
399     	}
400 
401     	return best;
402     }	
403 }