diff --git a/src/java/com/threerings/media/image/Quantize.java b/src/java/com/threerings/media/image/Quantize.java index 8448ec8f8..f0b2d31a6 100644 --- a/src/java/com/threerings/media/image/Quantize.java +++ b/src/java/com/threerings/media/image/Quantize.java @@ -1,5 +1,5 @@ -// -// $Id: Quantize.java,v 1.4 2004/08/27 02:12:38 mdb Exp $ +// +// $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved @@ -18,758 +18,759 @@ // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -package com.threerings.media.image; - -/* - * @(#)Quantize.java 0.90 9/19/00 Adam Doppelt - */ - -/** - * Calculates a reduced color - * - * Three Rings note: Code taken from - * Adam Doppelt, who - * adapted it from other code. Feel the love.
- * - * RenderingHints is supposed to provide a way to block dithering, but I have - * not been able to get that to work. It always dithers, so we use this - * class instead. - *
- * - * The following modifications were added to the original code: - * - Made it work with image data with transparent pixels. - * - Clarified documentation of the main method. - * - Changed the 'QUICK' constant to false for better quantization. - * - *
- * - * Original headers follow: - * - * - * - * - * An efficient color quantization algorithm, adapted from the C++ - * implementation quantize.c in ImageMagick. 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.
- * - * Here is the copyright notice from ImageMagick: - * - *
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% 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. %
-% %
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
- *
- *
- * @version 0.90 19 Sep 2000
- * @author Adam Doppelt
- */
-public class Quantize {
-
-/*
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% %
-% %
-% %
-% 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 = false;
-
- 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.
- *
- * @param pixels an in/out parameter that should initially contain
- * [A]RGB values but that will contain color palette indicies upon return.
- *
- * @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[];
-
- // do we have transparent pixels?
- boolean hasTrans = false;
-
- 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;
- if (alpha != 255) {
- hasTrans = true;
- continue; // don't add transparent pixels to the cube
- }
- 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));
- if (node.child[id] == null) {
- new Node(node, id, level);
- }
- node = node.child[id];
- node.number_pixels += SHIFT[level];
- }
-
- ++node.unique;
- node.total_red += red;
- node.total_green += green;
- node.total_blue += blue;
- }
- }
-
- // if we have transparent pixels, that cuts into the number
- // of other colors we can use.
- if (hasTrans) {
- this.max_colors--;
- }
- }
-
- /*
- * 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() {
- int threshold = 1;
- while (colors > max_colors) {
- colors = 0;
- threshold = root.reduce(threshold, Integer.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();
-
- int transPad = hasTrans ? 1 : 0;
-
- // 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;
- if (alpha != 255) {
- pixels[x][y] = 0; // transparent
- continue;
- }
- 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) );
- 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 + transPad;
- } else {
- // Find the closest color.
- search.distance = Integer.MAX_VALUE;
- node.parent.closestColor(red, green, blue, search);
- pixels[x][y] = search.color_number + transPad;
- }
- }
- }
-
- // expand the colormap by one to account for the transparent
- if (hasTrans) {
- int[] newcmap = new int[colormap.length + 1];
- System.arraycopy(colormap, 0, newcmap, 1, colormap.length);
- colormap = newcmap;
- }
- }
-
- /**
- * 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_red;
- int mid_green;
- int mid_blue;
-
- // the pixel count for this node and all children
- int number_pixels;
-
- // the pixel count for this node
- int unique;
- // the sum of all pixels contained in this node
- 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[8];
- this.id = 0;
- this.level = 0;
-
- this.number_pixels = Integer.MAX_VALUE;
-
- this.mid_red = (MAX_RGB + 1) >> 1;
- this.mid_green = (MAX_RGB + 1) >> 1;
- this.mid_blue = (MAX_RGB + 1) >> 1;
- }
-
- Node(Node parent, int id, int level) {
- this.cube = parent.cube;
- this.parent = parent;
- this.child = new Node[8];
- 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);
- }
-
- /**
- * Remove this child node, and make sure our parent
- * absorbs our pixel statistics.
- */
- void pruneChild() {
- --parent.nchild;
- parent.unique += unique;
- 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 < 8; 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
- */
- int reduce(int threshold, int next_threshold) {
- if (nchild != 0) {
- for (int id = 0; id < 8; 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 < 8; id++) {
- if (child[id] != null) {
- child[id].colormap();
- }
- }
- }
- if (unique != 0) {
- 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] = ((( 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 red, int green, int blue, Search search) {
- if (nchild != 0) {
- for (int id = 0; id < 8; id++) {
- if (child[id] != null) {
- child[id].closestColor(red, green, blue, search);
- }
- }
- }
-
- if (unique != 0) {
- int color = cube.colormap[color_number];
- int distance = distance(color, 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 r, int g, int b) {
- return (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_red);
- buf.append(',');
- buf.append(mid_green);
- buf.append(',');
- buf.append(mid_blue);
- buf.append(']');
- return new String(buf);
- }
- }
- }
-}
+package com.threerings.media.image;
+
+/*
+ * @(#)Quantize.java 0.90 9/19/00 Adam Doppelt
+ */
+
+/**
+ * Calculates a reduced color
+ *
+ * Three Rings note: Code taken from
+ * Adam Doppelt, who
+ * adapted it from other code. Feel the love.+ * + * RenderingHints is supposed to provide a way to block dithering, but I have + * not been able to get that to work. It always dithers, so we use this + * class instead. + *
+ * + * The following modifications were added to the original code: + * - Made it work with image data with transparent pixels. + * - Clarified documentation of the main method. + * - Changed the 'QUICK' constant to false for better quantization. + * - Fixed an integer overflow that caused a bug quantizing large images. + * + *
+ * + * Original headers follow: + * + * + * + * + * An efficient color quantization algorithm, adapted from the C++ + * implementation quantize.c in ImageMagick. 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.
+ * + * Here is the copyright notice from ImageMagick: + * + *
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% 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. %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ *
+ *
+ * @version 0.90 19 Sep 2000
+ * @author Adam Doppelt
+ */
+public class Quantize {
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% 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 = false;
+
+ 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.
+ *
+ * @param pixels an in/out parameter that should initially contain
+ * [A]RGB values but that will contain color palette indicies upon return.
+ *
+ * @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[];
+
+ // do we have transparent pixels?
+ boolean hasTrans = false;
+
+ 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;
+ if (alpha != 255) {
+ hasTrans = true;
+ continue; // don't add transparent pixels to the cube
+ }
+ 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));
+ if (node.child[id] == null) {
+ new Node(node, id, level);
+ }
+ node = node.child[id];
+ node.number_pixels += SHIFT[level];
+ }
+
+ ++node.unique;
+ node.total_red += red;
+ node.total_green += green;
+ node.total_blue += blue;
+ }
+ }
+
+ // if we have transparent pixels, that cuts into the number
+ // of other colors we can use.
+ if (hasTrans) {
+ this.max_colors--;
+ }
+ }
+
+ /*
+ * 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();
+
+ int transPad = hasTrans ? 1 : 0;
+
+ // 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;
+ if (alpha != 255) {
+ pixels[x][y] = 0; // transparent
+ continue;
+ }
+ 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) );
+ 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 + transPad;
+ } else {
+ // Find the closest color.
+ search.distance = Integer.MAX_VALUE;
+ node.parent.closestColor(red, green, blue, search);
+ pixels[x][y] = search.color_number + transPad;
+ }
+ }
+ }
+
+ // expand the colormap by one to account for the transparent
+ if (hasTrans) {
+ int[] newcmap = new int[colormap.length + 1];
+ System.arraycopy(colormap, 0, newcmap, 1, colormap.length);
+ colormap = newcmap;
+ }
+ }
+
+ /**
+ * 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_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_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[8];
+ 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;
+ }
+
+ Node(Node parent, int id, int level) {
+ this.cube = parent.cube;
+ this.parent = parent;
+ this.child = new Node[8];
+ 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);
+ }
+
+ /**
+ * Remove this child node, and make sure our parent
+ * absorbs our pixel statistics.
+ */
+ void pruneChild() {
+ --parent.nchild;
+ parent.unique += unique;
+ 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 < 8; 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 < 8; 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 < 8; id++) {
+ if (child[id] != null) {
+ child[id].colormap();
+ }
+ }
+ }
+ if (unique != 0) {
+ 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] = ((( 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 red, int green, int blue, Search search) {
+ if (nchild != 0) {
+ for (int id = 0; id < 8; id++) {
+ if (child[id] != null) {
+ child[id].closestColor(red, green, blue, search);
+ }
+ }
+ }
+
+ if (unique != 0) {
+ int color = cube.colormap[color_number];
+ int distance = distance(color, 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 r, int g, int b) {
+ return (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_red);
+ buf.append(',');
+ buf.append(mid_green);
+ buf.append(',');
+ buf.append(mid_blue);
+ buf.append(']');
+ return new String(buf);
+ }
+ }
+ }
+}