Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
MedianCut.java
1 package ij.process;
2 
3 import java.awt.*;
4 import java.awt.image.*;
5 import ij.*; //??
6 
11 public class MedianCut {
12 
13  static final int MAXCOLORS = 256; // maximum # of output colors
14  static final int HSIZE = 32768; // size of image histogram
15  private int[] hist; // RGB histogram and reverse color lookup table
16  private int[] histPtr; // points to colors in "hist"
17  private Cube[] list; // list of cubes
18  private int[] pixels32;
19  private int width, height;
20  private IndexColorModel cm;
21 
22  public MedianCut(int[] pixels, int width, int height) {
23  int color16;
24 
25  pixels32 = pixels;
26  this.width = width;
27  this.height = height;
28 
29  //build 32x32x32 RGB histogram
30  IJ.showProgress(0.3);
31  IJ.showStatus("Building 32x32x32 RGB histogram");
32  hist = new int[HSIZE];
33  for (int i=0; i<width*height; i++) {
34  color16 = rgb(pixels32[i]);
35  hist[color16]++;
36  }
37  }
38 
39  public MedianCut(ColorProcessor ip) {
40  this((int[])ip.getPixels(), ip.getWidth(), ip.getHeight());
41  }
42 
43  int getColorCount() {
44  int count = 0;
45  for (int i=0; i<HSIZE; i++)
46  if (hist[i]>0) count++;
47  return count;
48  }
49 
50 
51  Color getModalColor() {
52  int max=0;
53  int c = 0;
54  for (int i=0; i<HSIZE; i++)
55  if (hist[i]>max) {
56  max = hist[i];
57  c = i;
58  }
59  return new Color(red(c), green(c), blue(c));
60  }
61 
62 
63  // Convert from 24-bit to 15-bit color
64  private final int rgb(int c) {
65  int r = (c&0xf80000)>>19;
66  int g = (c&0xf800)>>6;
67  int b = (c&0xf8)<<7;
68  return b | g | r;
69  }
70 
71  // Get red component of a 15-bit color
72  private final int red(int x) {
73  return (x&31)<<3;
74  }
75 
76  // Get green component of a 15-bit color
77  private final int green(int x) {
78  return (x>>2)&0xf8;
79  }
80 
81  // Get blue component of a 15-bit color
82  private final int blue(int x) {
83  return (x>>7)&0xf8;
84  }
85 
86 
91  public Image convert(int maxcubes) {
92  ImageProcessor ip = convertToByte(maxcubes);
93  return ip.createImage();
94  }
95 
97  public ImageProcessor convertToByte(int maxcubes) {
98  int lr, lg, lb;
99  int i, median, color;
100  int count;
101  int k, level, ncubes, splitpos;
102  int num, width;
103  int longdim=0; //longest dimension of cube
104  Cube cube, cubeA, cubeB;
105 
106  // Create initial cube
107  IJ.showStatus("Median cut");
108  list = new Cube[MAXCOLORS];
109  histPtr = new int[HSIZE];
110  ncubes = 0;
111  cube = new Cube();
112  for (i=0,color=0; i<=HSIZE-1; i++) {
113  if (hist[i] != 0) {
114  histPtr[color++] = i;
115  cube.count = cube.count + hist[i];
116  }
117  }
118  cube.lower = 0; cube.upper = color-1;
119  cube.level = 0;
120  Shrink(cube);
121  list[ncubes++] = cube;
122 
123  //Main loop
124  while (ncubes < maxcubes) {
125 
126  // Search the list of cubes for next cube to split, the lowest level cube
127  level = 255; splitpos = -1;
128  for (k=0; k<=ncubes-1; k++) {
129  if (list[k].lower == list[k].upper)
130  ; // single color; cannot be split
131  else if (list[k].level < level) {
132  level = list[k].level;
133  splitpos = k;
134  }
135  }
136  if (splitpos == -1) // no more cubes to split
137  break;
138 
139  // Find longest dimension of this cube
140  cube = list[splitpos];
141  lr = cube.rmax - cube.rmin;
142  lg = cube.gmax - cube.gmin;
143  lb = cube.bmax - cube.bmin;
144  if (lr >= lg && lr >= lb) longdim = 0;
145  if (lg >= lr && lg >= lb) longdim = 1;
146  if (lb >= lr && lb >= lg) longdim = 2;
147 
148  // Sort along "longdim"
149  reorderColors(histPtr, cube.lower, cube.upper, longdim);
150  quickSort(histPtr, cube.lower, cube.upper);
151  restoreColorOrder(histPtr, cube.lower, cube.upper, longdim);
152 
153  // Find median
154  count = 0;
155  for (i=cube.lower;i<=cube.upper-1;i++) {
156  if (count >= cube.count/2) break;
157  color = histPtr[i];
158  count = count + hist[color];
159  }
160  median = i;
161 
162  // Now split "cube" at the median and add the two new
163  // cubes to the list of cubes.
164  cubeA = new Cube();
165  cubeA.lower = cube.lower;
166  cubeA.upper = median-1;
167  cubeA.count = count;
168  cubeA.level = cube.level + 1;
169  Shrink(cubeA);
170  list[splitpos] = cubeA; // add in old slot
171 
172  cubeB = new Cube();
173  cubeB.lower = median;
174  cubeB.upper = cube.upper;
175  cubeB.count = cube.count - count;
176  cubeB.level = cube.level + 1;
177  Shrink(cubeB);
178  list[ncubes++] = cubeB; // add in new slot */
179  if (ncubes%15==0)
180  IJ.showProgress(0.3 + (0.6*ncubes)/maxcubes);
181  }
182 
183  // We have enough cubes, or we have split all we can. Now
184  // compute the color map, the inverse color map, and return
185  // an 8-bit image.
186  IJ.showProgress(0.9);
187  makeInverseMap(hist, ncubes);
188  IJ.showProgress(0.95);
189  return makeImage();
190  }
191 
192  void Shrink(Cube cube) {
193  // Encloses "cube" with a tight-fitting cube by updating the
194  // (rmin,gmin,bmin) and (rmax,gmax,bmax) members of "cube".
195 
196  int r, g, b;
197  int color;
198  int rmin, rmax, gmin, gmax, bmin, bmax;
199 
200  rmin = 255; rmax = 0;
201  gmin = 255; gmax = 0;
202  bmin = 255; bmax = 0;
203  for (int i=cube.lower; i<=cube.upper; i++) {
204  color = histPtr[i];
205  r = red(color);
206  g = green(color);
207  b = blue(color);
208  if (r > rmax) rmax = r;
209  if (r < rmin) rmin = r;
210  if (g > gmax) gmax = g;
211  if (g < gmin) gmin = g;
212  if (b > bmax) bmax = b;
213  if (b < bmin) bmin = b;
214  }
215  cube.rmin = rmin; cube.rmax = rmax;
216  cube.gmin = gmin; cube.gmax = gmax;
217  cube.gmin = gmin; cube.gmax = gmax;
218  }
219 
220 
221  void makeInverseMap(int[] hist, int ncubes) {
222  // For each cube in the list of cubes, computes the centroid
223  // (average value) of the colors enclosed by that cube, and
224  // then loads the centroids in the color map. Next loads
225  // "hist" with indices into the color map
226 
227  int r, g, b;
228  int color;
229  float rsum, gsum, bsum;
230  Cube cube;
231  byte[] rLUT = new byte[256];
232  byte[] gLUT = new byte[256];
233  byte[] bLUT = new byte[256];
234 
235  IJ.showStatus("Making inverse map");
236  for (int k=0; k<=ncubes-1; k++) {
237  cube = list[k];
238  rsum = gsum = bsum = (float)0.0;
239  for (int i=cube.lower; i<=cube.upper; i++) {
240  color = histPtr[i];
241  r = red(color);
242  rsum += (float)r*(float)hist[color];
243  g = green(color);
244  gsum += (float)g*(float)hist[color];
245  b = blue(color);
246  bsum += (float)b*(float)hist[color];
247  }
248 
249  // Update the color map
250  r = (int)(rsum/(float)cube.count);
251  g = (int)(gsum/(float)cube.count);
252  b = (int)(bsum/(float)cube.count);
253  if (r==248 && g==248 && b==248)
254  r=g=b=255; // Restore white (255,255,255)
255  rLUT[k] = (byte)r;
256  gLUT[k] = (byte)g;
257  bLUT[k] = (byte)b;
258  }
259  cm = new IndexColorModel(8, ncubes, rLUT, gLUT, bLUT);
260 
261  // For each color in each cube, load the corre-
262  // sponding slot in "hist" with the centroid of the cube.
263  for (int k=0; k<=ncubes-1; k++) {
264  cube = list[k];
265  for (int i=cube.lower; i<=cube.upper; i++) {
266  color = histPtr[i];
267  hist[color] = k;
268  }
269  }
270  }
271 
272 
273  void reorderColors(int[] a, int lo, int hi, int longDim) {
274  // Change the ordering of the 5-bit colors in each word of int[]
275  // so we can sort on the 'longDim' color
276 
277  int c, r, g, b;
278  switch (longDim) {
279  case 0: //red
280  for (int i=lo; i<=hi; i++) {
281  c = a[i];
282  r = c & 31;
283  a[i] = (r<<10) | (c>>5);
284  }
285  break;
286  case 1: //green
287  for (int i=lo; i<=hi; i++) {
288  c = a[i];
289  r = c & 31;
290  g = (c>>5) & 31;
291  b = c>>10;
292  a[i] = (g<<10) | (b<<5) | r;
293  }
294  break;
295  case 2: //blue; already in the needed order
296  break;
297  }
298  }
299 
300 
301  void restoreColorOrder(int[] a, int lo, int hi, int longDim) {
302  // Restore the 5-bit colors to the original order
303 
304  int c, r, g, b;
305  switch (longDim){
306  case 0: //red
307  for (int i=lo; i<=hi; i++) {
308  c = a[i];
309  r = c >> 10;
310  a[i] = ((c&1023)<<5) | r;
311  }
312  break;
313  case 1: //green
314  for (int i=lo; i<=hi; i++) {
315  c = a[i];
316  r = c & 31;
317  g = c>>10;
318  b = (c>>5) & 31;
319  a[i] = (b<<10) | (g<<5) | r;
320  }
321  break;
322  case 2: //blue
323  break;
324  }
325  }
326 
327 
328  void quickSort(int a[], int lo0, int hi0) {
329  // Based on the QuickSort method by James Gosling from Sun's SortDemo applet
330 
331  int lo = lo0;
332  int hi = hi0;
333  int mid, t;
334 
335  if ( hi0 > lo0) {
336  mid = a[ ( lo0 + hi0 ) / 2 ];
337  while( lo <= hi ) {
338  while( ( lo < hi0 ) && ( a[lo] < mid ) )
339  ++lo;
340  while( ( hi > lo0 ) && ( a[hi] > mid ) )
341  --hi;
342  if( lo <= hi ) {
343  t = a[lo];
344  a[lo] = a[hi];
345  a[hi] = t;
346  ++lo;
347  --hi;
348  }
349  }
350  if( lo0 < hi )
351  quickSort( a, lo0, hi );
352  if( lo < hi0 )
353  quickSort( a, lo, hi0 );
354 
355  }
356  }
357 
358 
359  ImageProcessor makeImage() {
360  // Generate 8-bit image
361 
362  Image img8;
363  byte[] pixels8;
364  int color16;
365 
366  IJ.showStatus("Creating 8-bit image");
367  pixels8 = new byte[width*height];
368  for (int i=0; i<width*height; i++) {
369  color16 = rgb(pixels32[i]);
370  pixels8[i] = (byte)hist[color16];
371  }
372  ImageProcessor ip = new ByteProcessor(width, height, pixels8, cm);
373  IJ.showProgress(1.0);
374  return ip;
375  }
376 
377 
378 } //class MedianCut
379 
380 
381 class Cube { // structure for a cube in color space
382  int lower; // one corner's index in histogram
383  int upper; // another corner's index in histogram
384  int count; // cube's histogram count
385  int level; // cube's level
386  int rmin, rmax;
387  int gmin, gmax;
388  int bmin, bmax;
389 
390  Cube() {
391  count = 0;
392  }
393 
394  public String toString() {
395  String s = "lower=" + lower + " upper=" + upper;
396  s = s + " count=" + count + " level=" + level;
397  s = s + " rmin=" + rmin + " rmax=" + rmax;
398  s = s + " gmin=" + gmin + " gmax=" + gmax;
399  s = s + " bmin=" + bmin + " bmax=" + bmax;
400  return s;
401  }
402 
403 }
404