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.quantize;
011
012import java.util.ArrayList;
013import java.util.LinkedList;
014import java.util.List;
015
016import org.apache.commons.math3.ml.clustering.CentroidCluster;
017import org.apache.commons.math3.ml.clustering.DoublePoint;
018import org.apache.commons.math3.ml.clustering.KMeansPlusPlusClusterer;
019
020
021/**
022 * This class implements color quantization by k-means clustering
023 * of image pixels in RGB color space. It uses the Apache Commons
024 * Math class {@link KMeansPlusPlusClusterer} to perform the
025 * clustering.
026 * 
027 * @author WB
028 * @version 2017/01/04
029 */
030public class KMeansClusteringQuantizerApache extends ColorQuantizer {
031        
032        private final Parameters params;
033        private final int[][] colormap;
034        
035//      private final Clusterer<DoublePoint> clusterer;
036        private final List<CentroidCluster<DoublePoint>> centers;
037        
038        public enum SamplingMethod {
039                Random, Most_Frequent
040        };
041        
042        public static class Parameters {
043                /** Maximum number of quantized colors. */
044                public int maxColors = 16;
045                /** Maximum number of clustering iterations */
046                public int maxIterations = 500;
047                
048                void check() {
049                        if (maxColors < 2 || maxColors > 256 || maxIterations < 1) {
050                                throw new IllegalArgumentException();
051                        }
052                }
053        }
054        
055        // --------------------------------------------------------------
056
057        /**
058         * Creates a new quantizer instance from the supplied sequence
059         * of color values (assumed to be ARGB-encoded integers). 
060         * @param pixels Sequence of input color values.
061         * @param params Parameter object.
062         */
063        public KMeansClusteringQuantizerApache(int[] pixels, Parameters params) {
064                params.check();
065                this.params = params;
066                centers = cluster(pixels);
067                colormap = makeColorMap();
068        }
069        
070        public KMeansClusteringQuantizerApache(int[] pixels) {
071                this(pixels, new Parameters());
072        }
073        
074        // --------------------------------------------------------------
075
076        private List<CentroidCluster<DoublePoint>> cluster(int[] pixels) {
077                KMeansPlusPlusClusterer<DoublePoint> clusterer = 
078                                new KMeansPlusPlusClusterer<>(params.maxColors, params.maxIterations);
079                
080                List<DoublePoint> points = new ArrayList<>();
081                for (int i = 0; i < pixels.length; i++) {
082                        points.add(new DoublePoint(intToRgbDouble(pixels[i])));
083                }
084                
085                return clusterer.cluster(points);
086        }
087        
088        // --------------------------------------------------------------
089
090        private double[] intToRgbDouble(int rgb) {
091                int red   = ((rgb >> 16) & 0xFF);
092                int grn = ((rgb >> 8) & 0xFF);
093                int blu  = (rgb & 0xFF);
094                return new double[] {red, grn, blu};
095        }
096
097        private int[][] makeColorMap() {
098                List<int[]> colList = new LinkedList<>();
099                
100                for (CentroidCluster<DoublePoint> ctr : centers) {
101                        double[] c = ctr.getCenter().getPoint();
102                        int red = (int) Math.round(c[0]);
103                        int grn = (int) Math.round(c[1]);
104                        int blu = (int) Math.round(c[2]);
105                        colList.add(new int[] {red, grn, blu});
106                }
107                
108                return colList.toArray(new int[0][]);
109        }
110        
111        /**
112         * Lists the color clusters to System.out (intended for debugging only).
113         */
114//      public void listClusters() {
115//              for (Cluster c : clusters) {
116//                      System.out.println(c.toString());
117//              }
118//      }
119        
120
121        // ------- methods required by abstract super class -----------------------
122        
123        @Override
124        public int[][] getColorMap() {
125                return colormap;
126        }
127        
128} 
129