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.Arrays;
013import java.util.LinkedList;
014import java.util.List;
015
016/**
017 * This class implements color quantization based on the octree method. It is a
018 * re-factored version of an implementation (OctTreeOpImage.java) used in Sun's JAI 
019 * (Java Advanced Imaging) framework, which – in turn – is based on a 
020 * C implementation (quantize.c) authored by John Cristy in 1992 as part of 
021 * <a href="http://www.imagemagick.org/">ImageMagick</a>.
022 * The associated source code can be found 
023 * <a href="http://git.imagemagick.org/repos/ImageMagick/blob/master/MagickCore/quantize.c">
024 * here</a>, the original copyright note is provided at the bottom of this source file.
025 * 
026 * See also the abovementioned JAI implementation in
027 * <a href="https://java.net/projects/jai-core/sources/svn/content/trunk/src/share/classes/com/sun/media/jai/opimage/OctTreeOpImage.java">
028 * OctTreeOpImage.java</a>.
029 * 
030 * This implementation is similar but not identical to the original octree quantization algorithm proposed
031 * by Gervautz and Purgathofer [M. Gervautz and W. Purgathofer. "A simple method for color
032 * quantization: octree quantization", in A. Glassner (editor), Graphics Gems I, 
033 * pp. 287–293. Academic Press, New York (1990)].
034 * This implementation uses a modified strategy for pruning the octree for better efficiency.
035 * 
036 * "Quick quantization" means that original colors are mapped to their color index by simply
037 * finding the containing octree node. Otherwise, the closest quantized color (in terms of 
038 * Euclidean distance) is searched for, which is naturally slower.
039 * 
040 * @author WB
041 * @version 2017/01/03
042 */
043public class OctreeQuantizer extends ColorQuantizer {
044
045//      private final static int MAX_RGB = 255;
046        private final static int MAX_NODES = 262144 - 1; // = 2^18 - 1, was 266817;
047        private final static int MAX_TREE_DEPTH = 8;    // check, 7 enough?
048
049        private final int maxColors;    // max. number of distinct colors after quantization
050        
051        private final int nColors;              // final number of colors
052        private final Node root;                // root node of the tree
053        
054        private int depth;                              // current depth of the tree
055        private int nodeCnt = 0;                // counts the number of nodes in the tree
056        private int colorCnt = 0;               // counts the number of colors in the cube (used for temp. counting)
057
058        private final int[][] colormap;
059        private boolean quickQuantization = false;
060        
061        private final Parameters params;
062        
063        public static class Parameters {
064                /** Maximum number of quantized colors. */
065                public int maxColors = 16;
066                /** Enable/disable quick quantization */
067                public boolean quickQuantization = false;
068                
069                void check() {
070                        if (maxColors < 2 || maxColors > 256) {
071                                throw new IllegalArgumentException();
072                        }
073                }
074        }
075
076        // -------------------------------------------------------------------------
077        
078        public OctreeQuantizer(int[] pixels, Parameters params) {
079                this.params = params;
080                this.maxColors = this.params.maxColors;
081                this.root = new Node(null, 0);
082                this.depth = Math.min(Math.max(log2(maxColors) - 1, 2), MAX_TREE_DEPTH);// 2 <= depth <= maxTreeDepth   
083                int initColorCnt = addPixels(pixels);
084                this.nColors = reduceTree(initColorCnt, pixels.length);
085                this.colormap = makeColorMap();
086        }
087        
088        public OctreeQuantizer(int[] pixels) {
089                this(pixels, new Parameters());
090        }
091        
092        // -------------------------------------------------------------------------
093        
094        private int addPixels(int[] pixels) {
095                for (int p : pixels) {
096                        addPixel(p);
097                        if (nodeCnt > MAX_NODES) { // a hard limit on the number of nodes in the tree
098                                root.pruneTree();
099                                depth--;
100                        }       
101                }
102                return colorCnt;
103        }
104
105        private void addPixel(int p) {
106                // walk the tree to depth, increasing the number_pixels count in each node
107                Node node = root;
108                node.nPixels++;
109                int[] rgb = intToRgb(p);
110                for (int level = 1; level <= depth; level++) {
111                        int id = node.getChildId(rgb);
112                        if (node.childs[id] == null) {
113                                node = new Node(node, id);      // create a new node at next level
114                        } 
115                        else {  // step to next level
116                                node = node.childs[id];
117                        }
118                        node.nPixels++;
119                }
120
121                // at level 'depth': update color statistics of this node
122                node.nUnique++;
123                node.totalRed += rgb[0];
124                node.totalGrn += rgb[1];
125                node.totalBlu += rgb[2];
126        }
127
128        /**
129         * reduceTree() repeatedly prunes the tree until the number of
130         * nodes with unique > 0 is less than or equal to the maximum
131         * number of colors allowed in the output image.
132         * When a node to be pruned has offspring, the pruning
133         * procedure invokes itself recursively in order to prune the
134         * tree from the leaves upward.  The statistics of the node
135         * being pruned are always added to the corresponding data in
136         * that node's parent.  This retains the pruned node's color
137         * characteristics for later averaging.
138         * 
139         * @param initColorCnt 
140         *              The initial number of colors (leaves) in the octree.
141         * @param nSamples 
142         *              The total number of color samples used for creating the tree.
143         * @return
144         */
145        private int reduceTree(int initColorCnt, int nSamples) {
146                int minPixelCnt = Math.max(1,  nSamples / (maxColors * 8));
147                colorCnt = initColorCnt;
148                while (colorCnt > maxColors) {
149                        colorCnt = 0;   // for recounting the number of colors (leaves)
150                        minPixelCnt = root.reduceSparseNodes(minPixelCnt, Integer.MAX_VALUE);
151                }
152                return colorCnt;
153        }
154
155        private int[][] makeColorMap() {
156                List<int[]> colList = new LinkedList<>();
157                colorCnt = 0;   // used to store the color index in each node
158                root.collectColors(colList);
159                return colList.toArray(new int[0][]);
160        }
161        
162        /**
163         * Lists the octree nodes to System.out (intended for debugging only).
164         */
165        public void listNodes() {
166                root.listNodes();
167        }
168
169        // ------------------------------------------------------------------------------------
170        
171        /**
172         * Represents a node in the octree.
173         */
174        private class Node {
175                final Node parent;              // reference to the parent node (null for the root node)
176                final Node[] childs;    // references to child nodes
177                final int id;                   // branch index of this node at the parent node
178                final int level;                // level of this node within the tree (root has level 0)
179                
180                int nChilds = 0;        // number of child nodes
181                int nPixels = 0;        // number of pixels represented by this node and all child nodes
182                int nUnique = 0;        // number of pixels represented by this node but none of the children
183
184                final int midRed;       // RGB color midpoint (center of this node in color space)
185                final int midGrn;
186                final int midBlu;
187                
188                int totalRed = 0;       // sum of all pixel component values represented by this node
189                int totalGrn = 0;
190                int totalBlu = 0;
191                
192                int colorIdx = -1;      // the index of the associated color in the final color table
193
194                Node(Node parent, int id) {
195                        this.parent = parent;
196                        this.id = id;
197                        this.childs = new Node[8];
198                        nodeCnt++;
199
200                        if (parent == null) {   // this is the root node
201                                this.level = 0;
202                                // set this node's midpoint
203                                int mid = (MAX_RGB + 1) / 2;
204                                midRed = mid;
205                                midGrn = mid;
206                                midBlu = mid;
207                        }
208                        else {
209                                this.level = parent.level + 1;
210                                // attach this node to its parent
211                                parent.nChilds++;
212                                parent.childs[id] = this;
213                                // set this node's midpoint
214                                //int bi = (1 << (MAX_TREE_DEPTH - level)) >> 1;
215                                int bi = 256 >> (level + 1);
216                                midRed = parent.midRed + ((id & 1) > 0 ? bi : -bi);
217                                midGrn = parent.midGrn + ((id & 2) > 0 ? bi : -bi);
218                                midBlu = parent.midBlu + ((id & 4) > 0 ? bi : -bi);
219                                if (level == depth) {   // this is a leaf node
220                                        colorCnt++;
221                                }
222                        }
223                }
224
225
226                /**
227                 * Prune all nodes at the lowest level (= depth) of the tree.
228                 */
229                void pruneTree() {
230                        if (nChilds != 0) {     
231                                // move recursively to the lowest level
232                                for (int i = 0; i < childs.length; i++) {
233                                        if (childs[i] != null) {
234                                                childs[i].pruneTree();
235                                        }
236                                }
237                        }
238                        // work on the actual node now
239                        if (level == depth) {   // we are at a node on the lowest level
240                                delete();                       // remove THIS node!
241                        }
242                }
243
244
245                /**
246                 * Removes any nodes that hold fewer than 'minPixelCount' pixels
247                 * and finds the minimum population of all remaining nodes. 
248                 * 
249                 * @param minPixelCount The minimum population required for surviving nodes.
250                 * @param newMinPixelCnt The minimum population found so far (during recursive tree walk).
251                 * @return
252                 */
253                int reduceSparseNodes(int minPixelCount, int newMinPixelCnt) {
254                        // visit all child nodes first
255                        if (nChilds > 0) {
256                                for (Node ch : childs) {
257                                        if (ch != null) {
258                                                newMinPixelCnt = ch.reduceSparseNodes(minPixelCount, newMinPixelCnt);
259                                        }
260                                }
261                        }
262                        // work on the actual node now
263                        if (nPixels <= minPixelCount) {
264                                this.delete();
265                        } 
266                        else {
267                                if (nUnique > 0) {
268                                        colorCnt++;
269                                }
270                                newMinPixelCnt = Math.min(nPixels, newMinPixelCnt); // find smallest population
271                        }
272                        return newMinPixelCnt;
273                }
274                
275                /**
276                 * Remove this node, and push all pixel statistics to its parent.
277                 */
278                void delete() {
279                        parent.nChilds--;
280                        parent.nUnique += nUnique;
281                        parent.totalRed += totalRed;
282                        parent.totalGrn += totalGrn;
283                        parent.totalBlu += totalBlu;
284                        parent.childs[id] = null;       // unlink
285                        nodeCnt--;
286                }
287                
288                /**
289                 * Calculates the branch index [0,...,7] out of this node
290                 * for the specified color.
291                 * @param red
292                 * @param grn
293                 * @param blu
294                 * @return The branch index [0,...,7].
295                 */
296                int getChildId(int[] rgb) {
297                        int idx = ((rgb[0] > this.midRed ? 1 : 0) << 0) 
298                                        | ((rgb[1] > this.midGrn ? 1 : 0) << 1)
299                                        | ((rgb[2] > this.midBlu ? 1 : 0) << 2);
300                        return idx;
301                }
302                
303                /**
304                 * Collects the color entries for the color map. Any node
305                 * with a non-zero number of unique colors creates a color map
306                 * entry. The representative color for the node is calculated
307                 * as the average color vector over all contributing pixels.
308                 * 
309                 * @param colList List of colors to add to.
310                 */
311                private void collectColors(List<int[]> colList) {
312                        // visit all children first
313                        if (nChilds > 0) {
314                                for (Node ch : childs) {
315                                        if (ch != null) {
316                                                ch.collectColors(colList);
317                                        }
318                                }
319                        }
320                        
321                        // process this node
322                        if (nUnique > 0) {                      
323                                int avgRed = (totalRed + (nUnique / 2)) / nUnique;
324                                int avgGrn = (totalGrn + (nUnique / 2)) / nUnique;
325                                int avgBlu = (totalBlu + (nUnique / 2)) / nUnique;
326                                colList.add(new int[] {avgRed, avgGrn, avgBlu});
327                                this.colorIdx = colorCnt;       // store the color table index for this node
328                                colorCnt++;
329                        }
330                }
331
332                private void listNodes() {
333                        if (nChilds > 0) {      
334                                for (Node ch : childs) {
335                                        if (ch != null) {
336                                                ch.listNodes();
337                                        }
338                                }
339                        }
340                        // bottom of recursion
341                        System.out.format(makeBlanks(level) + "node level=%d unique=%d total=%d: %3d %3d %3d, \n", 
342                                        level, nUnique, this.nPixels, 0xFF & midRed, 0xFF & midGrn, 0xFF & midBlu); 
343                }
344                
345                private String makeBlanks(int level) {
346                        char[] blanks = new char[3 * level];
347                        Arrays.fill(blanks, '-');
348                        return new String(blanks);
349                }
350        }
351        
352        // ------- methods required by abstract super class -----------------------
353        
354        @Override
355        public int[][] getColorMap() {
356                return colormap;
357        }
358        
359        @Override
360        protected int findColorIndex(int p) {
361                if (quickQuantization) {
362                        return getNodeIndex(p);
363                }
364                else {
365                        return super.findColorIndex(p);
366                }
367        }
368
369        /**
370         * Finds the associated color table index for the supplied RGB color
371         * by traversing the octree. 
372         * @param p
373         * @return
374         */
375        private int getNodeIndex(int p) {
376                int[] rgb = intToRgb(p);
377                Node node = root;
378                while(true) {
379                        int id = node.getChildId(rgb);
380                        if (node.childs[id] == null) {
381                                break;
382                        }
383                        node = node.childs[id];
384                }
385                // this node is associated with color p
386                if (node.colorIdx < 0) {
387                        throw new IllegalArgumentException("cannot assign color " + p);
388                }
389                return node.colorIdx;
390        }
391        
392}
393
394/*
395%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
396%                                                                             %
397%                                                                             %
398%                                                                             %
399%           QQQ   U   U   AAA   N   N  TTTTT  IIIII   ZZZZZ  EEEEE            %
400%          Q   Q  U   U  A   A  NN  N    T      I        ZZ  E                %
401%          Q   Q  U   U  AAAAA  N N N    T      I      ZZZ   EEEEE            %
402%          Q  QQ  U   U  A   A  N  NN    T      I     ZZ     E                %
403%           QQQQ   UUU   A   A  N   N    T    IIIII   ZZZZZ  EEEEE            %
404%                                                                             %
405%                                                                             %
406%              Reduce the Number of Unique Colors in an Image                 %
407%                                                                             %
408%                                                                             %
409%                           Software Design                                   %
410%                             John Cristy                                     %
411%                              July 1992                                      %
412%                                                                             %
413%                                                                             %
414%  Copyright 1998 E. I. du Pont de Nemours and Company                        %
415%                                                                             %
416%  Permission is hereby granted, free of charge, to any person obtaining a    %
417%  copy of this software and associated documentation files ("ImageMagick"),  %
418%  to deal in ImageMagick without restriction, including without limitation   %
419%  the rights to use, copy, modify, merge, publish, distribute, sublicense,   %
420%  and/or sell copies of ImageMagick, and to permit persons to whom the       %
421%  ImageMagick is furnished to do so, subject to the following conditions:    %
422%                                                                             %
423%  The above copyright notice and this permission notice shall be included in %
424%  all copies or substantial portions of ImageMagick.                         %
425%                                                                             %
426%  The software is provided "as is", without warranty of any kind, express or %
427%  implied, including but not limited to the warranties of merchantability,   %
428%  fitness for a particular purpose and noninfringement.  In no event shall   %
429%  E. I. du Pont de Nemours and Company be liable for any claim, damages or   %
430%  other liability, whether in an action of contract, tort or otherwise,      %
431%  arising from, out of or in connection with ImageMagick or the use or other %
432%  dealings in ImageMagick.                                                   %
433%                                                                             %
434%  Except as contained in this notice, the name of the E. I. du Pont de       %
435%  Nemours and Company shall not be used in advertising or otherwise to       %
436%  promote the sale, use or other dealings in ImageMagick without prior       %
437%  written authorization from the E. I. du Pont de Nemours and Company.       %
438%                                                                             %
439%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
440%
441%  Realism in computer graphics typically requires using 24 bits/pixel to
442%  generate an image. Yet many graphic display devices do not contain
443%  the amount of memory necessary to match the spatial and color
444%  resolution of the human eye. The QUANTIZE program takes a 24 bit
445%  image and reduces the number of colors so it can be displayed on
446%  raster device with less bits per pixel. In most instances, the
447%  quantized image closely resembles the original reference image.
448%
449%  A reduction of colors in an image is also desirable for image
450%  transmission and real-time animation.
451%
452%  Function Quantize takes a standard RGB or monochrome images and quantizes
453%  them down to some fixed number of colors.
454%
455%  For purposes of color allocation, an image is a set of n pixels, where
456%  each pixel is a point in RGB space. RGB space is a 3-dimensional
457%  vector space, and each pixel, pi, is defined by an ordered triple of
458%  red, green, and blue coordinates, (ri, gi, bi).
459%
460%  Each primary color component (red, green, or blue) represents an
461%  intensity which varies linearly from 0 to a maximum value, cmax, which
462%  corresponds to full saturation of that color. Color allocation is
463%  defined over a domain consisting of the cube in RGB space with
464%  opposite vertices at (0,0,0) and (cmax,cmax,cmax). QUANTIZE requires
465%  cmax = 255.
466%
467%  The algorithm maps this domain onto a tree in which each node
468%  represents a cube within that domain. In the following discussion
469%  these cubes are defined by the coordinate of two opposite vertices:
470%  The vertex nearest the origin in RGB space and the vertex farthest
471%  from the origin.
472%
473%  The tree's root node represents the the entire domain, (0,0,0) through
474%  (cmax,cmax,cmax). Each lower level in the tree is generated by
475%  subdividing one node's cube into eight smaller cubes of equal size.
476%  This corresponds to bisecting the parent cube with planes passing
477%  through the midpoints of each edge.
478%
479%  The basic algorithm operates in three phases: Classification,
480%  Reduction, and Assignment. Classification builds a color
481%  description tree for the image. Reduction collapses the tree until
482%  the number it represents, at most, the number of colors desired in the
483%  output image. Assignment defines the output image's color map and
484%  sets each pixel's color by reclassification in the reduced tree.
485%  Our goal is to minimize the numerical discrepancies between the original
486%  colors and quantized colors (quantization error).
487%
488%  Classification begins by initializing a color description tree of
489%  sufficient depth to represent each possible input color in a leaf.
490%  However, it is impractical to generate a fully-formed color
491%  description tree in the classification phase for realistic values of
492%  cmax. If colors components in the input image are quantized to k-bit
493%  precision, so that cmax= 2k-1, the tree would need k levels below the
494%  root node to allow representing each possible input color in a leaf.
495%  This becomes prohibitive because the tree's total number of nodes is
496%  1 + sum(i=1,k,8k).
497%
498%  A complete tree would require 19,173,961 nodes for k = 8, cmax = 255.
499%  Therefore, to avoid building a fully populated tree, QUANTIZE: (1)
500%  Initializes data structures for nodes only as they are needed;  (2)
501%  Chooses a maximum depth for the tree as a function of the desired
502%  number of colors in the output image (currently log2(colormap size)).
503%
504%  For each pixel in the input image, classification scans downward from
505%  the root of the color description tree. At each level of the tree it
506%  identifies the single node which represents a cube in RGB space
507%  containing the pixel's color. It updates the following data for each
508%  such node:
509%
510%    n1: Number of pixels whose color is contained in the RGB cube
511%    which this node represents;
512%
513%    n2: Number of pixels whose color is not represented in a node at
514%    lower depth in the tree;  initially,  n2 = 0 for all nodes except
515%    leaves of the tree.
516%
517%    Sr, Sg, Sb: Sums of the red, green, and blue component values for
518%    all pixels not classified at a lower depth. The combination of
519%    these sums and n2  will ultimately characterize the mean color of a
520%    set of pixels represented by this node.
521%
522%    E: The distance squared in RGB space between each pixel contained
523%    within a node and the nodes' center. This represents the quantization
524%    error for a node.
525%
526%  Reduction repeatedly prunes the tree until the number of nodes with
527%  n2 > 0 is less than or equal to the maximum number of colors allowed
528%  in the output image. On any given iteration over the tree, it selects
529%  those nodes whose E count is minimal for pruning and merges their
530%  color statistics upward. It uses a pruning threshold, Ep, to govern
531%  node selection as follows:
532%
533%    Ep = 0
534%    while number of nodes with (n2 > 0) > required maximum number of colors
535%      prune all nodes such that E <= Ep
536%      Set Ep to minimum E in remaining nodes
537%
538%  This has the effect of minimizing any quantization error when merging
539%  two nodes together.
540%
541%  When a node to be pruned has offspring, the pruning procedure invokes
542%  itself recursively in order to prune the tree from the leaves upward.
543%  n2,  Sr, Sg,  and  Sb in a node being pruned are always added to the
544%  corresponding data in that node's parent. This retains the pruned
545%  node's color characteristics for later averaging.
546%
547%  For each node, n2 pixels exist for which that node represents the
548%  smallest volume in RGB space containing those pixel's colors. When n2
549%  > 0 the node will uniquely define a color in the output image. At the
550%  beginning of reduction,  n2 = 0  for all nodes except a the leaves of
551%  the tree which represent colors present in the input image.
552%
553%  The other pixel count, n1, indicates the total number of colors
554%  within the cubic volume which the node represents. This includes n1 -
555%  n2  pixels whose colors should be defined by nodes at a lower level in
556%  the tree.
557%
558%  Assignment generates the output image from the pruned tree. The
559%  output image consists of two parts: (1)  A color map, which is an
560%  array of color descriptions (RGB triples) for each color present in
561%  the output image;  (2)  A pixel array, which represents each pixel as
562%  an index into the color map array.
563%
564%  First, the assignment phase makes one pass over the pruned color
565%  description tree to establish the image's color map. For each node
566%  with n2  > 0, it divides Sr, Sg, and Sb by n2 . This produces the
567%  mean color of all pixels that classify no lower than this node. Each
568%  of these colors becomes an entry in the color map.
569%
570%  Finally,  the assignment phase reclassifies each pixel in the pruned
571%  tree to identify the deepest node containing the pixel's color. The
572%  pixel's value in the pixel array becomes the index of this node's mean
573%  color in the color map.
574%
575%  With the permission of USC Information Sciences Institute, 4676 Admiralty
576%  Way, Marina del Rey, California  90292, this code was adapted from module
577%  ALCOLS written by Paul Raveling.
578%
579%  The names of ISI and USC are not used in advertising or publicity
580%  pertaining to distribution of the software without prior specific
581%  written permission from ISI.
582%
583*/