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*/