package doppelt; /** * A modified version of Quantize.java that handles alpha. */ /* * @(#)Quantize.java 0.90 9/19/00 Adam Doppelt */ /** * An efficient color quantization algorithm, adapted from the C++ * implementation quantize.c in <a * href="http://www.imagemagick.org/">ImageMagick</a>. The pixels for * an image are placed into an oct tree. The oct tree is reduced in * size, and the pixels from the original image are reassigned to the * nodes in the reduced tree.<p> * * Here is the copyright notice from ImageMagick: * * <pre> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Permission is hereby granted, free of charge, to any person obtaining a % % copy of this software and associated documentation files ("ImageMagick"), % % to deal in ImageMagick without restriction, including without limitation % % the rights to use, copy, modify, merge, publish, distribute, sublicense, % % and/or sell copies of ImageMagick, and to permit persons to whom the % % ImageMagick is furnished to do so, subject to the following conditions: % % % % The above copyright notice and this permission notice shall be included in % % all copies or substantial portions of ImageMagick. % % % % The software is provided "as is", without warranty of any kind, express or % % implied, including but not limited to the warranties of merchantability, % % fitness for a particular purpose and noninfringement. In no event shall % % E. I. du Pont de Nemours and Company be liable for any claim, damages or % % other liability, whether in an action of contract, tort or otherwise, % % arising from, out of or in connection with ImageMagick or the use or other % % dealings in ImageMagick. % % % % Except as contained in this notice, the name of the E. I. du Pont de % % Nemours and Company shall not be used in advertising or otherwise to % % promote the sale, use or other dealings in ImageMagick without prior % % written authorization from the E. I. du Pont de Nemours and Company. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% </pre> * * * @version 0.90 19 Sep 2000 * @author Adam Doppelt <http://www.gurge.com/amd/> */ //package doppelt; public class Quantize32 { /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % QQQ U U AAA N N TTTTT IIIII ZZZZZ EEEEE % % Q Q U U A A NN N T I ZZ E % % Q Q U U AAAAA N N N T I ZZZ EEEEE % % Q QQ U U A A N NN T I ZZ E % % QQQQ UUU A A N N T IIIII ZZZZZ EEEEE % % % % % % Reduce the Number of Unique Colors in an Image % % % % % % Software Design % % John Cristy % % July 1992 % % % % % % Copyright 1998 E. I. du Pont de Nemours and Company % % % % Permission is hereby granted, free of charge, to any person obtaining a % % copy of this software and associated documentation files ("ImageMagick"), % % to deal in ImageMagick without restriction, including without limitation % % the rights to use, copy, modify, merge, publish, distribute, sublicense, % % and/or sell copies of ImageMagick, and to permit persons to whom the % % ImageMagick is furnished to do so, subject to the following conditions: % % % % The above copyright notice and this permission notice shall be included in % % all copies or substantial portions of ImageMagick. % % % % The software is provided "as is", without warranty of any kind, express or % % implied, including but not limited to the warranties of merchantability, % % fitness for a particular purpose and noninfringement. In no event shall % % E. I. du Pont de Nemours and Company be liable for any claim, damages or % % other liability, whether in an action of contract, tort or otherwise, % % arising from, out of or in connection with ImageMagick or the use or other % % dealings in ImageMagick. % % % % Except as contained in this notice, the name of the E. I. du Pont de % % Nemours and Company shall not be used in advertising or otherwise to % % promote the sale, use or other dealings in ImageMagick without prior % % written authorization from the E. I. du Pont de Nemours and Company. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Realism in computer graphics typically requires using 24 bits/pixel to % generate an image. Yet many graphic display devices do not contain % the amount of memory necessary to match the spatial and color % resolution of the human eye. The QUANTIZE program takes a 24 bit % image and reduces the number of colors so it can be displayed on % raster device with less bits per pixel. In most instances, the % quantized image closely resembles the original reference image. % % A reduction of colors in an image is also desirable for image % transmission and real-time animation. % % Function Quantize takes a standard RGB or monochrome images and quantizes % them down to some fixed number of colors. % % For purposes of color allocation, an image is a set of n pixels, where % each pixel is a point in RGB space. RGB space is a 3-dimensional % vector space, and each pixel, pi, is defined by an ordered triple of % red, green, and blue coordinates, (ri, gi, bi). % % Each primary color component (red, green, or blue) represents an % intensity which varies linearly from 0 to a maximum value, cmax, which % corresponds to full saturation of that color. Color allocation is % defined over a domain consisting of the cube in RGB space with % opposite vertices at (0,0,0) and (cmax,cmax,cmax). QUANTIZE requires % cmax = 255. % % The algorithm maps this domain onto a tree in which each node % represents a cube within that domain. In the following discussion % these cubes are defined by the coordinate of two opposite vertices: % The vertex nearest the origin in RGB space and the vertex farthest % from the origin. % % The tree's root node represents the the entire domain, (0,0,0) through % (cmax,cmax,cmax). Each lower level in the tree is generated by % subdividing one node's cube into eight smaller cubes of equal size. % This corresponds to bisecting the parent cube with planes passing % through the midpoints of each edge. % % The basic algorithm operates in three phases: Classification, % Reduction, and Assignment. Classification builds a color % description tree for the image. Reduction collapses the tree until % the number it represents, at most, the number of colors desired in the % output image. Assignment defines the output image's color map and % sets each pixel's color by reclassification in the reduced tree. % Our goal is to minimize the numerical discrepancies between the original % colors and quantized colors (quantization error). % % Classification begins by initializing a color description tree of % sufficient depth to represent each possible input color in a leaf. % However, it is impractical to generate a fully-formed color % description tree in the classification phase for realistic values of % cmax. If colors components in the input image are quantized to k-bit % precision, so that cmax= 2k-1, the tree would need k levels below the % root node to allow representing each possible input color in a leaf. % This becomes prohibitive because the tree's total number of nodes is % 1 + sum(i=1,k,8k). % % A complete tree would require 19,173,961 nodes for k = 8, cmax = 255. % Therefore, to avoid building a fully populated tree, QUANTIZE: (1) % Initializes data structures for nodes only as they are needed; (2) % Chooses a maximum depth for the tree as a function of the desired % number of colors in the output image (currently log2(colormap size)). % % For each pixel in the input image, classification scans downward from % the root of the color description tree. At each level of the tree it % identifies the single node which represents a cube in RGB space % containing the pixel's color. It updates the following data for each % such node: % % n1: Number of pixels whose color is contained in the RGB cube % which this node represents; % % n2: Number of pixels whose color is not represented in a node at % lower depth in the tree; initially, n2 = 0 for all nodes except % leaves of the tree. % % Sr, Sg, Sb: Sums of the red, green, and blue component values for % all pixels not classified at a lower depth. The combination of % these sums and n2 will ultimately characterize the mean color of a % set of pixels represented by this node. % % E: The distance squared in RGB space between each pixel contained % within a node and the nodes' center. This represents the quantization % error for a node. % % Reduction repeatedly prunes the tree until the number of nodes with % n2 > 0 is less than or equal to the maximum number of colors allowed % in the output image. On any given iteration over the tree, it selects % those nodes whose E count is minimal for pruning and merges their % color statistics upward. It uses a pruning threshold, Ep, to govern % node selection as follows: % % Ep = 0 % while number of nodes with (n2 > 0) > required maximum number of colors % prune all nodes such that E <= Ep % Set Ep to minimum E in remaining nodes % % This has the effect of minimizing any quantization error when merging % two nodes together. % % When a node to be pruned has offspring, the pruning procedure invokes % itself recursively in order to prune the tree from the leaves upward. % n2, Sr, Sg, and Sb in a node being pruned are always added to the % corresponding data in that node's parent. This retains the pruned % node's color characteristics for later averaging. % % For each node, n2 pixels exist for which that node represents the % smallest volume in RGB space containing those pixel's colors. When n2 % > 0 the node will uniquely define a color in the output image. At the % beginning of reduction, n2 = 0 for all nodes except a the leaves of % the tree which represent colors present in the input image. % % The other pixel count, n1, indicates the total number of colors % within the cubic volume which the node represents. This includes n1 - % n2 pixels whose colors should be defined by nodes at a lower level in % the tree. % % Assignment generates the output image from the pruned tree. The % output image consists of two parts: (1) A color map, which is an % array of color descriptions (RGB triples) for each color present in % the output image; (2) A pixel array, which represents each pixel as % an index into the color map array. % % First, the assignment phase makes one pass over the pruned color % description tree to establish the image's color map. For each node % with n2 > 0, it divides Sr, Sg, and Sb by n2 . This produces the % mean color of all pixels that classify no lower than this node. Each % of these colors becomes an entry in the color map. % % Finally, the assignment phase reclassifies each pixel in the pruned % tree to identify the deepest node containing the pixel's color. The % pixel's value in the pixel array becomes the index of this node's mean % color in the color map. % % With the permission of USC Information Sciences Institute, 4676 Admiralty % Way, Marina del Rey, California 90292, this code was adapted from module % ALCOLS written by Paul Raveling. % % The names of ISI and USC are not used in advertising or publicity % pertaining to distribution of the software without prior specific % written permission from ISI. % */ final static boolean QUICK = true; final static int MAX_RGB = 255; final static int MAX_NODES = 266817; final static int MAX_TREE_DEPTH = 8; // these are precomputed in advance static int SQUARES[]; static int SHIFT[]; static { SQUARES = new int[MAX_RGB + MAX_RGB + 1]; for (int i= -MAX_RGB; i <= MAX_RGB; i++) { SQUARES[i + MAX_RGB] = i * i; } SHIFT = new int[MAX_TREE_DEPTH + 1]; for (int i = 0; i < MAX_TREE_DEPTH + 1; ++i) { SHIFT[i] = 1 << (15 - i); } } /** * Reduce the image to the given number of colors. The pixels are * reduced in place. * @return The new color palette. */ public static int[] quantizeImage(int pixels[][], int max_colors) { Cube cube = new Cube(pixels, max_colors); cube.classification(); cube.reduction(); cube.assignment(); return cube.colormap; } static class Cube { int pixels[][]; int max_colors; int colormap[]; Node root; int depth; // counter for the number of colors in the cube. this gets // recalculated often. int colors; // counter for the number of nodes in the tree int nodes; Cube(int pixels[][], int max_colors) { this.pixels = pixels; this.max_colors = max_colors; int i = max_colors; // tree_depth = log max_colors // 4 for (depth = 1; i != 0; depth++) { i /= 4; } if (depth > 1) { --depth; } if (depth > MAX_TREE_DEPTH) { depth = MAX_TREE_DEPTH; } else if (depth < 2) { depth = 2; } root = new Node(this); } /* * Procedure Classification begins by initializing a color * description tree of sufficient depth to represent each * possible input color in a leaf. However, it is impractical * to generate a fully-formed color description tree in the * classification phase for realistic values of cmax. If * colors components in the input image are quantized to k-bit * precision, so that cmax= 2k-1, the tree would need k levels * below the root node to allow representing each possible * input color in a leaf. This becomes prohibitive because the * tree's total number of nodes is 1 + sum(i=1,k,8k). * * A complete tree would require 19,173,961 nodes for k = 8, * cmax = 255. Therefore, to avoid building a fully populated * tree, QUANTIZE: (1) Initializes data structures for nodes * only as they are needed; (2) Chooses a maximum depth for * the tree as a function of the desired number of colors in * the output image (currently log2(colormap size)). * * For each pixel in the input image, classification scans * downward from the root of the color description tree. At * each level of the tree it identifies the single node which * represents a cube in RGB space containing It updates the * following data for each such node: * * number_pixels : Number of pixels whose color is contained * in the RGB cube which this node represents; * * unique : Number of pixels whose color is not represented * in a node at lower depth in the tree; initially, n2 = 0 * for all nodes except leaves of the tree. * * total_red/green/blue : Sums of the red, green, and blue * component values for all pixels not classified at a lower * depth. The combination of these sums and n2 will * ultimately characterize the mean color of a set of pixels * represented by this node. */ void classification() { int pixels[][] = this.pixels; int width = pixels.length; int height = pixels[0].length; // convert to indexed color for (int x = width; x-- > 0; ) { for (int y = height; y-- > 0; ) { int pixel = pixels[x][y]; int alpha = (pixel >> 24) & 0xFF; int red = (pixel >> 16) & 0xFF; int green = (pixel >> 8) & 0xFF; int blue = (pixel >> 0) & 0xFF; // a hard limit on the number of nodes in the tree if (nodes > MAX_NODES) { System.out.println("pruning"); root.pruneLevel(); --depth; } // walk the tree to depth, increasing the // number_pixels count for each node Node node = root; for (int level = 1; level <= depth; ++level) { int id = (((red > node.mid_red ? 1 : 0) << 0) | ((green > node.mid_green ? 1 : 0) << 1) | ((blue > node.mid_blue ? 1 : 0) << 2) | ((alpha > node.mid_alpha ? 1 : 0) << 3)); if (node.child[id] == null) { new Node(node, id, level); } node = node.child[id]; node.number_pixels += SHIFT[level]; } ++node.unique; node.total_alpha += alpha; node.total_red += red; node.total_green += green; node.total_blue += blue; } } } /* * reduction repeatedly prunes the tree until the number of * nodes with unique > 0 is less than or equal to the maximum * number of colors allowed in the output image. * * When a node to be pruned has offspring, the pruning * procedure invokes itself recursively in order to prune the * tree from the leaves upward. The statistics of the node * being pruned are always added to the corresponding data in * that node's parent. This retains the pruned node's color * characteristics for later averaging. */ void reduction() { long threshold = 1; while (colors > max_colors) { colors = 0; threshold = root.reduce(threshold, Long.MAX_VALUE); } } /** * The result of a closest color search. */ static class Search { int distance; int color_number; } /* * Procedure assignment generates the output image from the * pruned tree. The output image consists of two parts: (1) A * color map, which is an array of color descriptions (RGB * triples) for each color present in the output image; (2) A * pixel array, which represents each pixel as an index into * the color map array. * * First, the assignment phase makes one pass over the pruned * color description tree to establish the image's color map. * For each node with n2 > 0, it divides Sr, Sg, and Sb by n2. * This produces the mean color of all pixels that classify no * lower than this node. Each of these colors becomes an entry * in the color map. * * Finally, the assignment phase reclassifies each pixel in * the pruned tree to identify the deepest node containing the * pixel's color. The pixel's value in the pixel array becomes * the index of this node's mean color in the color map. */ void assignment() { colormap = new int[colors]; colors = 0; root.colormap(); int pixels[][] = this.pixels; int width = pixels.length; int height = pixels[0].length; Search search = new Search(); // convert to indexed color for (int x = width; x-- > 0; ) { for (int y = height; y-- > 0; ) { int pixel = pixels[x][y]; int alpha = (pixel >> 24) & 0xFF; int red = (pixel >> 16) & 0xFF; int green = (pixel >> 8) & 0xFF; int blue = (pixel >> 0) & 0xFF; // walk the tree to find the cube containing that color Node node = root; for ( ; ; ) { int id = (((red > node.mid_red ? 1 : 0) << 0) | ((green > node.mid_green ? 1 : 0) << 1) | ((blue > node.mid_blue ? 1 : 0) << 2) | ((alpha > node.mid_alpha ? 1 : 0) << 3)); if (node.child[id] == null) { break; } node = node.child[id]; } if (QUICK) { // if QUICK is set, just use that // node. Strictly speaking, this isn't // necessarily best match. pixels[x][y] = node.color_number; } else { // Find the closest color. search.distance = Integer.MAX_VALUE; node.parent.closestColor(alpha, red, green, blue, search); pixels[x][y] = search.color_number; } } } } /** * A single Node in the tree. */ static class Node { Cube cube; // parent node Node parent; // child nodes Node child[]; int nchild; // our index within our parent int id; // our level within the tree int level; // our color midpoint int mid_alpha; int mid_red; int mid_green; int mid_blue; // the pixel count for this node and all children long number_pixels; // the pixel count for this node int unique; // the sum of all pixels contained in this node int total_alpha; int total_red; int total_green; int total_blue; // used to build the colormap int color_number; Node(Cube cube) { this.cube = cube; this.parent = this; this.child = new Node[16]; this.id = 0; this.level = 0; this.number_pixels = Long.MAX_VALUE; this.mid_red = (MAX_RGB + 1) >> 1; this.mid_green = (MAX_RGB + 1) >> 1; this.mid_blue = (MAX_RGB + 1) >> 1; this.mid_alpha = (MAX_RGB + 1) >> 1; } Node(Node parent, int id, int level) { this.cube = parent.cube; this.parent = parent; this.child = new Node[16]; this.id = id; this.level = level; // add to the cube ++cube.nodes; if (level == cube.depth) { ++cube.colors; } // add to the parent ++parent.nchild; parent.child[id] = this; // figure out our midpoint int bi = (1 << (MAX_TREE_DEPTH - level)) >> 1; mid_red = parent.mid_red + ((id & 1) > 0 ? bi : -bi); mid_green = parent.mid_green + ((id & 2) > 0 ? bi : -bi); mid_blue = parent.mid_blue + ((id & 4) > 0 ? bi : -bi); mid_alpha = parent.mid_alpha + ((id & 8) > 0 ? bi : -bi); } /** * Remove this child node, and make sure our parent * absorbs our pixel statistics. */ void pruneChild() { --parent.nchild; parent.unique += unique; parent.total_alpha += total_alpha; parent.total_red += total_red; parent.total_green += total_green; parent.total_blue += total_blue; parent.child[id] = null; --cube.nodes; cube = null; parent = null; } /** * Prune the lowest layer of the tree. */ void pruneLevel() { if (nchild != 0) { for (int id = 0; id < child.length; id++) { if (child[id] != null) { child[id].pruneLevel(); } } } if (level == cube.depth) { pruneChild(); } } /** * Remove any nodes that have fewer than threshold * pixels. Also, as long as we're walking the tree: * * - figure out the color with the fewest pixels * - recalculate the total number of colors in the tree */ long reduce(long threshold, long next_threshold) { if (nchild != 0) { for (int id = 0; id < child.length; id++) { if (child[id] != null) { next_threshold = child[id].reduce(threshold, next_threshold); } } } if (number_pixels <= threshold) { pruneChild(); } else { if (unique != 0) { cube.colors++; } if (number_pixels < next_threshold) { next_threshold = number_pixels; } } return next_threshold; } /* * colormap traverses the color cube tree and notes each * colormap entry. A colormap entry is any node in the * color cube tree where the number of unique colors is * not zero. */ void colormap() { if (nchild != 0) { for (int id = 0; id < child.length; id++) { if (child[id] != null) { child[id].colormap(); } } } if (unique != 0) { int a = ((total_alpha + (unique >> 1)) / unique); int r = ((total_red + (unique >> 1)) / unique); int g = ((total_green + (unique >> 1)) / unique); int b = ((total_blue + (unique >> 1)) / unique); cube.colormap[cube.colors] = (((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0)); color_number = cube.colors++; } } /* ClosestColor traverses the color cube tree at a * particular node and determines which colormap entry * best represents the input color. */ void closestColor(int alpha, int red, int green, int blue, Search search) { if (nchild != 0) { for (int id = 0; id < child.length; id++) { if (child[id] != null) { child[id].closestColor(alpha, red, green, blue, search); } } } if (unique != 0) { int color = cube.colormap[color_number]; int distance = distance(color, alpha, red, green, blue); if (distance < search.distance) { search.distance = distance; search.color_number = color_number; } } } /** * Figure out the distance between this node and som color. */ final static int distance(int color, int a, int r, int g, int b) { return (SQUARES[((color >> 24) & 0xFF) - a + MAX_RGB] + SQUARES[((color >> 16) & 0xFF) - r + MAX_RGB] + SQUARES[((color >> 8) & 0xFF) - g + MAX_RGB] + SQUARES[((color >> 0) & 0xFF) - b + MAX_RGB]); } public String toString() { StringBuffer buf = new StringBuffer(); if (parent == this) { buf.append("root"); } else { buf.append("node"); } buf.append(' '); buf.append(level); buf.append(" ["); buf.append(mid_alpha); buf.append(','); buf.append(mid_red); buf.append(','); buf.append(mid_green); buf.append(','); buf.append(mid_blue); buf.append(']'); return new String(buf); } } } }