/Users/lyon/j4p/src/ip/color/MedianCut.java

1    package ip.color; 
2     
3    import j2d.ShortImageBean; 
4     
5    import java.awt.*; 
6    import java.awt.image.IndexColorModel; 
7    import java.awt.image.MemoryImageSource; 
8    import java.awt.image.BufferedImage; 
9     
10   /** 
11    * Converts an RGB image to 8-bit index color using Heckbert's median-cut 
12    * color quantization algorithm. Based on median.c by Anton Kruger from the 
13    * September, 1994 issue of Dr. Dobbs Journal. 
14    */ 
15    
16    
17   // Thanks go to Wayne Rasband for his contribution 
18   // of the MedianCut class. 
19   // Also for putting his Image/J program, 
20   // with source code,  into the public domain 
21   // (available at http://rsb.info.nih.gov/ij/). 
22    
23   public class MedianCut { 
24    
25       static final int MAXIMUM_NUMBER_OF_OUTPUT_COLORS = 256; 
26    
27    
28       static final int SIZE_OF_THE_IMAGE_HISTOGRAM = 32768; 
29    
30       private int[] RGB_HISTOGRAM_AND_REVERSE_CLUT; 
31    
32       private int[] pointersToColorsInHistogram; 
33    
34       private MedianCube cubeList[]; 
35    
36       private int[] pixels32; 
37       private int width, height; 
38       private IndexColorModel cm; 
39    
40       /** 
41        * For a bufferedImage instance, this will give 
42        * an instance of the <code>MedianCut</code>class. 
43        * 
44        * @param bi 
45        * @return 
46        */ 
47       public static MedianCut getMedianCut(BufferedImage bi) { 
48           ShortImageBean sib = new ShortImageBean(bi); 
49           return new MedianCut(sib.getPels(), 
50                   sib.getWidth(), 
51                   sib.getHeight()); 
52       } 
53    
54       public MedianCut(int pixels[], int width, int height) { 
55           int color16; 
56    
57           pixels32 = pixels; 
58           this.width = width; 
59           this.height = height; 
60    
61           //build 32x32x32 RGB histogram 
62           RGB_HISTOGRAM_AND_REVERSE_CLUT = new int[SIZE_OF_THE_IMAGE_HISTOGRAM]; 
63           for (int i = 0; i < width * height; i++) { 
64               color16 = to15BitColor(pixels32[i]); 
65               RGB_HISTOGRAM_AND_REVERSE_CLUT[color16]++; 
66           } 
67       } 
68    
69    
70       // Convert from 24-bit to 15-bit color 
71       private static final int to15BitColor(int color24Bit) { 
72           int r = (color24Bit & 0xf80000) >> 19; 
73           int g = (color24Bit & 0xf800) >> 6; 
74           int b = (color24Bit & 0xf8) << 7; 
75           return b | g | r; 
76       } 
77    
78       // Get red component of a 15-bit color 
79       private static final int getRed15BitColor(int color15Bit) { 
80           return (color15Bit & 31) << 3; 
81       } 
82    
83       // Get green component of a 15-bit color 
84       private static final int getGreen15BitColor(int color15Bit) { 
85           return (color15Bit >> 2) & 0xf8; 
86       } 
87    
88       // Get blue component of a 15-bit color 
89       private static final int getBlue15BitColor(int color15Bit) { 
90           return (color15Bit >> 7) & 0xf8; 
91       } 
92    
93    
94       public Image convert(int maxcubes) { 
95           // Uses Heckbert's median-cut algorithm 
96           // to divide the color space defined by 
97           // "hist" into "maxcubes" cubes. 
98           // The centroids (average value) of each cube 
99           // are are used to create a color table. 
100          // "hist" is then updated to function 
101          // as an inverse color map that is 
102          // used to generate an 8-bit image. 
103   
104          int lr, lg, lb; 
105          int i, median, color; 
106          int count; 
107          int k, level, ncubes, splitpos; 
108          int longdim = 0;    //longest dimension of cube 
109          MedianCube cube, cubeA, cubeB; 
110   
111          // Create initial cube 
112          cubeList = new MedianCube[MAXIMUM_NUMBER_OF_OUTPUT_COLORS]; 
113          pointersToColorsInHistogram = new int[SIZE_OF_THE_IMAGE_HISTOGRAM]; 
114          ncubes = 0; 
115          cube = new MedianCube(); 
116          for (i = 0, color = 0; i <= SIZE_OF_THE_IMAGE_HISTOGRAM - 1; i++) { 
117              if (RGB_HISTOGRAM_AND_REVERSE_CLUT[i] != 0) { 
118                  pointersToColorsInHistogram[color++] = i; 
119                  cube.count = cube.count + RGB_HISTOGRAM_AND_REVERSE_CLUT[i]; 
120              } 
121          } 
122          cube.lower = 0; 
123          cube.upper = color - 1; 
124          cube.level = 0; 
125          Shrink(cube); 
126          cubeList[ncubes++] = cube; 
127   
128          //Main loop 
129          while (ncubes < maxcubes) { 
130   
131              // Search the list of cubes for next cube to split, 
132              // the lowest level cube 
133              level = 255; 
134              splitpos = -1; 
135              for (k = 0; k <= ncubes - 1; k++) { 
136                  if (cubeList[k].lower == cubeList[k].upper) 
137                      ;   // single color; cannot be split 
138                  else if (cubeList[k].level < level) { 
139                      level = cubeList[k].level; 
140                      splitpos = k; 
141                  } 
142              } 
143              if (splitpos == -1) // no more cubes to split 
144                  break; 
145   
146              // Find longest dimension of this cube 
147              cube = cubeList[splitpos]; 
148              lr = cube.rmax - cube.rmin; 
149              lg = cube.gmax - cube.gmin; 
150              lb = cube.bmax - cube.bmin; 
151              if (lr >= lg && lr >= lb) longdim = 0; 
152              if (lg >= lr && lg >= lb) longdim = 1; 
153              if (lb >= lr && lb >= lg) longdim = 2; 
154   
155              // Sort along "longdim" 
156              reorderColors(pointersToColorsInHistogram, cube.lower, cube.upper, longdim); 
157              quickSort(pointersToColorsInHistogram, cube.lower, cube.upper); 
158              restoreColorOrder(pointersToColorsInHistogram, cube.lower, cube.upper, longdim); 
159   
160              // Find median 
161              count = 0; 
162              for (i = cube.lower; i <= cube.upper - 1; i++) { 
163                  if (count >= cube.count / 2) break; 
164                  color = pointersToColorsInHistogram[i]; 
165                  count = count + RGB_HISTOGRAM_AND_REVERSE_CLUT[color]; 
166              } 
167              median = i; 
168   
169              // Now split "cube" at the median and add the two new 
170              // cubes to the list of cubes. 
171              cubeA = new MedianCube(); 
172              cubeA.lower = cube.lower; 
173              cubeA.upper = median - 1; 
174              cubeA.count = count; 
175              cubeA.level = cube.level + 1; 
176              Shrink(cubeA); 
177              cubeList[splitpos] = cubeA;             // add in old slot 
178   
179              cubeB = new MedianCube(); 
180              cubeB.lower = median; 
181              cubeB.upper = cube.upper; 
182              cubeB.count = cube.count - count; 
183              cubeB.level = cube.level + 1; 
184              Shrink(cubeB); 
185              cubeList[ncubes++] = cubeB;             // add in new slot */ 
186          } 
187   
188          // We have enough cubes, or we have split all we can. Now 
189          // compute the color map, the inverse color map, and return 
190          // an 8-bit image. 
191          makeInverseMap(RGB_HISTOGRAM_AND_REVERSE_CLUT, ncubes); 
192          return get8BitImage(); 
193      } 
194   
195   
196      private void Shrink(MedianCube cube) { 
197          // Encloses "cube" with a tight-fitting cube by updating the 
198          // (rmin,gmin,bmin) and (rmax,gmax,bmax) members of "cube". 
199   
200          int r, g, b; 
201          int color; 
202          int rmin, rmax, gmin, gmax, bmin, bmax; 
203   
204          rmin = 255; 
205          rmax = 0; 
206          gmin = 255; 
207          gmax = 0; 
208          bmin = 255; 
209          bmax = 0; 
210          for (int i = cube.lower; i <= cube.upper; i++) { 
211              color = pointersToColorsInHistogram[i]; 
212              r = getRed15BitColor(color); 
213              g = getGreen15BitColor(color); 
214              b = getBlue15BitColor(color); 
215              if (r > rmax) rmax = r; 
216              if (r < rmin) rmin = r; 
217              if (g > gmax) gmax = g; 
218              if (g < gmin) gmin = g; 
219              if (b > bmax) bmax = b; 
220              if (b < bmin) bmin = b; 
221          } 
222          cube.rmin = rmin; 
223          cube.rmax = rmax; 
224          cube.gmin = gmin; 
225          cube.gmax = gmax; 
226          cube.gmin = gmin; 
227          cube.gmax = gmax; 
228      } 
229   
230   
231      private void makeInverseMap(int[] hist, int ncubes) { 
232   
233          // For each cube in the list of cubes, computes the centroid 
234          // (average value) of the colors enclosed by that cube, and 
235          // then loads the centroids in the color map. Next loads 
236          // "hist" with indices into the color map 
237   
238          int r, g, b; 
239          int color; 
240          float rsum, gsum, bsum; 
241          MedianCube cube; 
242          byte rLUT[] = new byte[256]; 
243          byte gLUT[] = new byte[256]; 
244          byte bLUT[] = new byte[256]; 
245   
246          for (int k = 0; k <= ncubes - 1; k++) { 
247              cube = cubeList[k]; 
248              rsum = gsum = bsum = 0.0f; 
249              for (int i = cube.lower; i <= cube.upper; i++) { 
250                  color = pointersToColorsInHistogram[i]; 
251                  r = getRed15BitColor(color); 
252                  rsum += (float) (r * hist[color]); 
253                  g = getGreen15BitColor(color); 
254                  gsum += (float) (g * hist[color]); 
255                  b = getBlue15BitColor(color); 
256                  bsum += (float) (b * hist[color]); 
257              } 
258   
259              // Update the color map 
260              r = (int) (rsum / (float) cube.count); 
261              g = (int) (gsum / (float) cube.count); 
262              b = (int) (bsum / (float) cube.count); 
263              // Now I have a cube centroid 
264              if (r == 248 && g == 248 && b == 248) 
265                  r = g = b = 255;  // Restore white (255,255,255) 
266              rLUT[k] = (byte) (r & 0xff); 
267              gLUT[k] = (byte) (g & 0xff); 
268              bLUT[k] = (byte) (b & 0xff); 
269          } 
270   
271          cm = new IndexColorModel(8, ncubes, rLUT, gLUT, bLUT); 
272   
273          // For each color in each cube, load the corre- 
274          // sponding slot in "hist" with the centroid of the cube. 
275          for (int k = 0; k <= ncubes - 1; k++) { 
276              cube = cubeList[k]; 
277              for (int i = cube.lower; i <= cube.upper; i++) { 
278                  color = pointersToColorsInHistogram[i]; 
279                  hist[color] = k; 
280              } 
281          } 
282      } 
283   
284  // This method is never invoked. 
285  // it is optimal,but slow...the search 
286  // for the best map is exhaustive.. DL 
287      private void makeInverseTable(int[] hist, int ncubes, 
288                            byte[] rLUT, byte[] gLUT, byte[] bLUT) { 
289          // For each color, find the entry in the color map 
290          // that has the smallest Euclidian distance from that 
291          // color. Record this information in "hist". 
292   
293          int r, g, b; 
294          int index, color; 
295          int dr, dg, db, d, dmin; 
296   
297          index = 0; 
298          for (int i = 0; i < SIZE_OF_THE_IMAGE_HISTOGRAM; i++) { 
299              if (hist[i] > 0) { 
300                  color = i; 
301                  r = getRed15BitColor(color); 
302                  g = getGreen15BitColor(color); 
303                  b = getBlue15BitColor(color); 
304                  dmin = 999999999; 
305                  for (int j = 0; j < ncubes; j++) { 
306                      dr = (rLUT[j] & 0xff) - r; 
307                      dg = (gLUT[j] & 0xff) - g; 
308                      db = (bLUT[j] & 0xff) - b; 
309                      d = dr * dr + dg * dg + db * db; 
310                      if (d == 0) { 
311                          index = j; 
312                          break; 
313                      } else if (d < dmin) { 
314                          dmin = d; 
315                          index = j; 
316                      } 
317                  } 
318                  hist[color] = index; 
319              } 
320          } 
321   
322      } 
323   
324   
325      private void reorderColors(int[] a, int lo, int hi, int longDim) { 
326          // Change the ordering of the 5-bit colors in each word of int[] 
327          // so we can sort on the 'longDim' color 
328   
329          int c, r, g, b; 
330          switch (longDim) { 
331              case 0: //red 
332                  for (int i = lo; i <= hi; i++) { 
333                      c = a[i]; 
334                      r = c & 31; 
335                      a[i] = (r << 10) | (c >> 5); 
336                  } 
337                  break; 
338              case 1: //green 
339                  for (int i = lo; i <= hi; i++) { 
340                      c = a[i]; 
341                      r = c & 31; 
342                      g = (c >> 5) & 31; 
343                      b = c >> 10; 
344                      a[i] = (g << 10) | (b << 5) | r; 
345                  } 
346                  break; 
347              case 2: //blue; already in the needed order 
348                  break; 
349          } 
350      } 
351   
352   
353      private void restoreColorOrder(int[] a, int lo, int hi, int longDim) { 
354          // Restore the 5-bit colors to the original order 
355   
356          int c, r, g, b; 
357          switch (longDim) { 
358              case 0: //red 
359                  for (int i = lo; i <= hi; i++) { 
360                      c = a[i]; 
361                      r = c >> 10; 
362                      a[i] = ((c & 1023) << 5) | r; 
363                  } 
364                  break; 
365              case 1: //green 
366                  for (int i = lo; i <= hi; i++) { 
367                      c = a[i]; 
368                      r = c & 31; 
369                      g = c >> 10; 
370                      b = (c >> 5) & 31; 
371                      a[i] = (b << 10) | (g << 5) | r; 
372                  } 
373                  break; 
374              case 2: //blue 
375                  break; 
376          } 
377      } 
378   
379   
380      private static void quickSort(int a[], int lo0, int hi0) { 
381          // Based on the QuickSort method by 
382          // James Gosling from Sun's SortDemo applet 
383   
384          int lo = lo0; 
385          int hi = hi0; 
386          int mid, t; 
387   
388          if (hi0 > lo0) { 
389              mid = a[(lo0 + hi0) / 2]; 
390              while (lo <= hi) { 
391                  while ((lo < hi0) && (a[lo] < mid)) 
392                      ++lo; 
393                  while ((hi > lo0) && (a[hi] > mid)) 
394                      --hi; 
395                  if (lo <= hi) { 
396                      t = a[lo]; 
397                      a[lo] = a[hi]; 
398                      a[hi] = t; 
399                      ++lo; 
400                      --hi; 
401                  } 
402              } 
403              if (lo0 < hi) 
404                  quickSort(a, lo0, hi); 
405              if (lo < hi0) 
406                  quickSort(a, lo, hi0); 
407   
408          } 
409      } 
410      /** 
411       * Using existing color lookup table, map 
412       * image into a 256 color image. 
413       * If the color is not present, you will have to search the table. 
414       * @param bi 
415       * @return 
416       */ 
417      public Image get8BitImage(BufferedImage bi) { 
418   
419          Image img8; 
420          byte[] pixels8; 
421          int color16; 
422          ShortImageBean sib = new ShortImageBean(bi); 
423          pixels32 = sib.getPels(); 
424          width = sib.getWidth(); 
425          height = sib.getHeight(); 
426   
427          pixels8 = new byte[width * height]; 
428          for (int i = 0; i < width * height; i++) { 
429              color16 = to15BitColor(pixels32[i]); 
430              pixels8[i] = (byte) (RGB_HISTOGRAM_AND_REVERSE_CLUT[color16] & 0xff); 
431          } 
432          img8 = 
433                  Toolkit.getDefaultToolkit().createImage( 
434                          new MemoryImageSource(width, 
435                          height, 
436                          cm, 
437                          pixels8, 
438                          0, 
439                          width)); 
440          return img8; 
441      } 
442      public Image get8BitImage() { 
443   
444          Image img8; 
445          byte[] pixels8; 
446          int color16; 
447   
448          pixels8 = new byte[width * height]; 
449          for (int i = 0; i < width * height; i++) { 
450              color16 = to15BitColor(pixels32[i]); 
451              pixels8[i] = (byte) (RGB_HISTOGRAM_AND_REVERSE_CLUT[color16] & 0xff); 
452          } 
453          img8 = 
454                  Toolkit.getDefaultToolkit().createImage( 
455                          new MemoryImageSource(width, 
456                                  height, 
457                                  cm, 
458                                  pixels8, 
459                                  0, 
460                                  width)); 
461          return img8; 
462      } 
463   
464   
465  } //class MedianCut 
466   
467   
468  class MedianCube { 
469      // structure for a cube in color space 
470      int lower;          // one corner's index in histogram 
471      int upper;          // another corner's index in histogram 
472      int count;          // cube's histogram count 
473      int level;          // cube's level 
474      int rmin, rmax; 
475      int gmin, gmax; 
476      int bmin, bmax; 
477   
478      MedianCube() { 
479          count = 0; 
480      } 
481   
482      public String toString() { 
483          String s = "lower=" + lower + " upper=" + upper; 
484          s = s + " count=" + count + " level=" + level; 
485          s = s + " rmin=" + rmin + " rmax=" + rmax; 
486          s = s + " gmin=" + gmin + " gmax=" + gmax; 
487          s = s + " bmin=" + bmin + " bmax=" + bmax; 
488          return s; 
489      } 
490   
491  } 
492   
493