001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 *  image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause 
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 
006 * Copyright (c) 2006-2016 Wilhelm Burger, Mark J. Burge. All rights reserved. 
007 * Visit http://imagingbook.com for additional details.
008 *******************************************************************************/
009
010package imagingbook.pub.color.statistics;
011
012import java.util.Arrays;
013
014/**
015 * This class calculates a color histogram of a set of colors (i.e., a color image).
016 * Only the unique colors are accounted for. Colors are supplied as ARGB-encoded
017 * integers (A = alpha values being ignored).
018 * Colors are internally sorted by their frequency (in descending order).
019 *  
020 * @author WB
021 * @version 2017/01/04
022 */
023public class ColorHistogram {
024        
025        private final ColorNode[] colornodes;
026        
027        public ColorHistogram(int[] pixelsOrig) {
028                this(pixelsOrig, false);
029        }
030        
031        /**
032         * Creates a color histogram instance from the supplied sequence
033         * of color pixel values (assumed to be ARGB-encoded integers).
034         * The original pixel values are not modified.
035         * 
036         * @param pixelsOrig Original pixel values (not modified).
037         * @param sortByFrequency Pass true to sort the final colors by descending frequency.
038         */
039        public ColorHistogram(int[] pixelsOrig, boolean sortByFrequency) {
040                int[] pixels = new int[pixelsOrig.length];
041                for (int i = 0; i < pixels.length; i++) {
042                        pixels[i] = 0xFFFFFF & pixelsOrig[i];   // remove nonzero alpha components
043                }
044                
045                Arrays.sort(pixels);    // this is why we need a copy of the input array
046                
047                // count unique colors:
048                int k = -1; // current color index
049                int curColor = -1;
050                for (int i = 0; i < pixels.length; i++) {
051                        if (pixels[i] != curColor) {
052                                k++;
053                                curColor = pixels[i];
054                        }
055                }
056                int nUnique = k + 1;    // number of unique colors
057                
058                colornodes = new ColorNode[nUnique];
059                
060                // tabulate and find frequency of unique colors:
061                k = -1; // current color index
062                curColor = -1;
063                for (int i = 0; i < pixels.length; i++) {
064                        if (pixels[i] != curColor) {    // found a new color
065                                k++;
066                                curColor = pixels[i];
067                                colornodes[k] = new ColorNode(curColor);
068                        }
069                        else {                                                  // still with the previous color
070                                colornodes[k].add(1);
071                        }
072                }
073                
074                if (sortByFrequency)
075                        Arrays.sort(colornodes);        // sort unique colors by descending frequency
076        }
077        
078        /**
079         * Returns the number of unique colors.
080         * @return The number of unique colors.
081         */
082        public int getNumberOfColors() {
083                return colornodes.length;
084        }
085        
086        /**
087         * Returns the unique color with the given index.
088         * Colors are sorted by (decreasing) frequency.
089         * @param index The color index.
090         * @return      The color, encoded as an ARGB integer (A is zero).
091         */
092        public int getColor(int index) {
093                return colornodes[index].rgb;
094        }
095        
096        /**
097         * Returns the frequency of the unique color with the given index.
098         * Colors are sorted by (decreasing) frequency.
099         * @param index The color index.
100         * @return      The frequency of the color.
101         */
102        public int getCount(int index) {
103                return colornodes[index].count;
104        }
105        
106        /**
107         * Lists the unique colors to System.out (intended for
108         * debugging only).
109         */
110        public void listUniqueColors() {
111                for (ColorNode cn : colornodes) {
112                        System.out.println(cn.toString());
113                }
114        }
115        
116        // --------------------------------------------------------------------------------
117        
118        private class ColorNode implements Comparable<ColorNode> {
119                private final int rgb;
120                private int count;
121
122                ColorNode(int rgb) {
123                        this.rgb = rgb;
124                        this.count = 1;
125                }
126
127                void add(int n) {
128                        count = count + n;      
129                }
130                
131                public String toString() {
132                        return String.format(ColorNode.class.getSimpleName() + " rgb=%d count=%d", rgb, count);
133                }
134
135                @Override
136                public int compareTo(ColorNode c2) {    // to sort by count (high counts first)
137                        if (this.count > c2.count) return -1;
138                        if (this.count < c2.count) return 1;
139                        else return 0;
140                }
141        }
142        
143}