Part of the new Imaging libary: produces a BufferedImage, with and IndexColorModel, handles alpha channels and dithering.

This commit is contained in:
lehni 2004-06-17 09:51:13 +00:00
parent b19370cdbc
commit 0f40d6142b

View file

@ -1,7 +1,38 @@
/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile$
* $Author$
* $Revision$
* $Date$
*/
package helma.image;
import java.awt.image.*;
/* /*
* @(#)Quantize.java 0.90 9/19/00 Adam Doppelt * @(#)Quantize.java 0.90 9/19/00 Adam Doppelt
*
* Modifications by JŸrg Lehni:
*
* - Support for alpha-channels.
* - Returns a BufferedImage of TYPE_BYTE_INDEXED with a IndexColorModel.
* - Dithering of images through helma.image.DiffusionFilterOp by setting
* the dither parameter to true.
* - Support for a transparent color, which is correctly rendered by GIFEncoder.
* All pixels with alpha < 0x80 are converted to this color when the parameter
* alphaToBitmask is set to true.
* - Removed the SQUARES lookup tables as multiplications of integer values
* shouldn't take more than one clock nowadays anyhow.
*/ */
package helma.image;
/** /**
* An efficient color quantization algorithm, adapted from the C++ * An efficient color quantization algorithm, adapted from the C++
@ -155,7 +186,7 @@ public class Quantize {
% Therefore, to avoid building a fully populated tree, QUANTIZE: (1) % Therefore, to avoid building a fully populated tree, QUANTIZE: (1)
% Initializes data structures for nodes only as they are needed; (2) % Initializes data structures for nodes only as they are needed; (2)
% Chooses a maximum depth for the tree as a function of the desired % Chooses a maximum depth for the tree as a function of the desired
% number of colors in the output image (currently log2(colormap size)). % number of colors in the output image (currently log2(colorMap size)).
% %
% For each pixel in the input image, classification scans downward from % 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 % the root of the color description tree. At each level of the tree it
@ -212,7 +243,7 @@ public class Quantize {
% the tree. % the tree.
% %
% Assignment generates the output image from the pruned tree. The % Assignment generates the output image from the pruned tree. The
% output image consists of two parts: (1) A color map, which is an % outpu t image consists of two parts: (1) A color map, which is an
% array of color descriptions (RGB triples) for each color present in % array of color descriptions (RGB triples) for each color present in
% the output image; (2) A pixel array, which represents each pixel as % the output image; (2) A pixel array, which represents each pixel as
% an index into the color map array. % an index into the color map array.
@ -243,16 +274,17 @@ public class Quantize {
final static int MAX_RGB = 255; final static int MAX_RGB = 255;
final static int MAX_NODES = 266817; final static int MAX_NODES = 266817;
final static int MAX_TREE_DEPTH = 8; final static int MAX_TREE_DEPTH = 8;
final static int MAX_CHILDREN = 16;
// these are precomputed in advance // these are precomputed in advance
static int SQUARES[]; // static int SQUARES[];
static int SHIFT[]; static int SHIFT[];
static { static {
SQUARES = new int[MAX_RGB + MAX_RGB + 1]; /*
for (int i= -MAX_RGB; i <= MAX_RGB; i++) { * SQUARES = new int[MAX_RGB + MAX_RGB + 1]; for (int i= -MAX_RGB; i <=
SQUARES[i + MAX_RGB] = i * i; * MAX_RGB; i++) { SQUARES[i + MAX_RGB] = i * i; }
} */
SHIFT = new int[MAX_TREE_DEPTH + 1]; SHIFT = new int[MAX_TREE_DEPTH + 1];
for (int i = 0; i < MAX_TREE_DEPTH + 1; ++i) { for (int i = 0; i < MAX_TREE_DEPTH + 1; ++i) {
@ -261,39 +293,63 @@ public class Quantize {
} }
/** /**
* Reduce the image to the given number of colors. The pixels are * Reduce the image to the given number of colors. The pixels are reduced in
* reduced in place. * place.
*
* @return The new color palette. * @return The new color palette.
*/ */
public static int[] quantizeImage(int pixels[][], int max_colors) { public static BufferedImage process(BufferedImage source, int maxColors,
Cube cube = new Cube(pixels, max_colors); boolean dither, boolean alphaToBitmask) {
int type = source.getType();
int[] pixels;
// try to get the direct pixels of the BufferedImage
// this works for images of type INT_RGB, INT_ARGB and INT_ARGB_PRE
// for all others, a new array with rgb pixels is created!
if (type == BufferedImage.TYPE_INT_RGB
|| type == BufferedImage.TYPE_INT_ARGB
|| type == BufferedImage.TYPE_INT_ARGB_PRE) {
pixels = ((DataBufferInt) source.getRaster().getDataBuffer()).getData();
} else {
pixels = source.getRGB(0, 0, source.getWidth(), source.getHeight(), null, 0, source.getWidth());
}
Cube cube = new Cube(source, pixels, maxColors, dither, alphaToBitmask);
cube.classification(); cube.classification();
cube.reduction(); cube.reduction();
cube.assignment(); return cube.assignment();
return cube.colormap;
} }
static class Cube { static class Cube {
int pixels[][]; BufferedImage source;
int max_colors; int[] pixels;
int colormap[]; int maxColors;
byte colorMap[][];
Node root; Node root;
int depth; int depth;
boolean dither;
boolean alphaToBitmask;
boolean addTransparency;
// firstColor is set to 1 when when addTransparency is true!
int firstColor = 0;
// counter for the number of colors in the cube. this gets // counter for the number of colors in the cube. this gets
// recalculated often. // recalculated often.
int colors; int numColors;
// counter for the number of nodes in the tree // counter for the number of nodes in the tree
int nodes; int numNodes;
Cube(int pixels[][], int max_colors) { Cube(BufferedImage source, int[] pixels, int maxColors, boolean dither,
boolean alphaToBitmask) {
this.source = source;
this.pixels = pixels; this.pixels = pixels;
this.max_colors = max_colors; this.maxColors = maxColors;
this.dither = dither;
this.alphaToBitmask = alphaToBitmask;
int i = max_colors; int i = maxColors;
// tree_depth = log max_colors // tree_depth = log maxColors
// 4 // 4
for (depth = 1; i != 0; depth++) { for (depth = 1; i != 0; depth++) {
i /= 4; i /= 4;
@ -311,102 +367,103 @@ public class Quantize {
} }
/* /*
* Procedure Classification begins by initializing a color * Procedure Classification begins by initializing a color description
* description tree of sufficient depth to represent each * tree of sufficient depth to represent each possible input color in a
* possible input color in a leaf. However, it is impractical * leaf. However, it is impractical to generate a fully-formed color
* to generate a fully-formed color description tree in the * description tree in the classification phase for realistic values of
* classification phase for realistic values of cmax. If * cmax. If colors components in the input image are quantized to k-bit
* 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
* precision, so that cmax= 2k-1, the tree would need k levels * root node to allow representing each possible input color in a leaf.
* below the root node to allow representing each possible * This becomes prohibitive because the tree's total number of nodes is
* input color in a leaf. This becomes prohibitive because the * 1 + sum(i=1,k,8k).
* 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, * A complete tree would require 19,173,961 nodes for k = 8, cmax = 255.
* cmax = 255. Therefore, to avoid building a fully populated * Therefore, to avoid building a fully populated tree, QUANTIZE: (1)
* tree, QUANTIZE: (1) Initializes data structures for nodes * Initializes data structures for nodes only as they are needed; (2)
* only as they are needed; (2) Chooses a maximum depth for * Chooses a maximum depth for the tree as a function of the desired
* the tree as a function of the desired number of colors in * number of colors in the output image (currently log2(colorMap size)).
* the output image (currently log2(colormap size)).
* *
* For each pixel in the input image, classification scans * For each pixel in the input image, classification scans downward from
* downward from the root of the color description tree. At * the root of the color description tree. At each level of the tree it
* each level of the tree it identifies the single node which * identifies the single node which represents a cube in RGB space
* represents a cube in RGB space containing It updates the * containing It updates the following data for each such node:
* following data for each such node:
* *
* number_pixels : Number of pixels whose color is contained * numPixels : Number of pixels whose color is contained in the RGB cube
* in the RGB cube which this node represents; * which this node represents;
* *
* unique : Number of pixels whose color is not represented * unique : Number of pixels whose color is not represented in a node at
* in a node at lower depth in the tree; initially, n2 = 0 * lower depth in the tree; initially, n2 = 0 for all nodes except
* for all nodes except leaves of the tree. * leaves of the tree.
* *
* total_red/green/blue : Sums of the red, green, and blue * totalRed/green/blue : Sums of the red, green, and blue component
* component values for all pixels not classified at a lower * values for all pixels not classified at a lower depth. The
* depth. The combination of these sums and n2 will * combination of these sums and n2 will ultimately characterize the
* ultimately characterize the mean color of a set of pixels * mean color of a set of pixels represented by this node.
* represented by this node.
*/ */
void classification() { void classification() {
int pixels[][] = this.pixels; addTransparency = false;
firstColor = 0;
int width = pixels.length; for (int i = 0; i < pixels.length; i++) {
int height = pixels[0].length; int pixel = pixels[i];
int red = (pixel >> 16) & 0xff;
// convert to indexed color int green = (pixel >> 8) & 0xff;
for (int x = width; x-- > 0; ) { int blue = (pixel >> 0) & 0xff;
for (int y = height; y-- > 0; ) { int alpha = (pixel >> 24) & 0xff;
int pixel = pixels[x][y]; if (alphaToBitmask)
int red = (pixel >> 16) & 0xFF; alpha = alpha < 0x80 ? 0 : 0xff;
int green = (pixel >> 8) & 0xFF;
int blue = (pixel >> 0) & 0xFF;
if (alpha > 0) {
// a hard limit on the number of nodes in the tree // a hard limit on the number of nodes in the tree
if (nodes > MAX_NODES) { if (numNodes > MAX_NODES) {
System.out.println("pruning"); // System.out.println("pruning");
root.pruneLevel(); root.pruneLevel();
--depth; --depth;
} }
// walk the tree to depth, increasing the // walk the tree to depth, increasing the
// number_pixels count for each node // numPixels count for each node
Node node = root; Node node = root;
for (int level = 1; level <= depth; ++level) { for (int level = 1; level <= depth; ++level) {
int id = (((red > node.mid_red ? 1 : 0) << 0) | int id = (((red > node.midRed ? 1 : 0) << 0)
((green > node.mid_green ? 1 : 0) << 1) | | ((green > node.midGreen ? 1 : 0) << 1)
((blue > node.mid_blue ? 1 : 0) << 2)); | ((blue > node.midBlue ? 1 : 0) << 2) | ((alpha > node.midAlpha ? 1
if (node.child[id] == null) { : 0) << 3));
if (node.children[id] == null) {
new Node(node, id, level); new Node(node, id, level);
} }
node = node.child[id]; node = node.children[id];
node.number_pixels += SHIFT[level]; node.numPixels += SHIFT[level];
} }
++node.unique; ++node.unique;
node.total_red += red; node.totalRed += red;
node.total_green += green; node.totalGreen += green;
node.total_blue += blue; node.totalBlue += blue;
node.totalAlpha += alpha;
} else if (!addTransparency) {
addTransparency = true;
numColors++;
firstColor = 1; // start at 1 as 0 will be the transparent
// color
} }
} }
} }
/* /*
* reduction repeatedly prunes the tree until the number of * reduction repeatedly prunes the tree until the number of nodes with
* nodes with unique > 0 is less than or equal to the maximum * unique > 0 is less than or equal to the maximum number of colors
* number of colors allowed in the output image. * allowed in the output image.
* *
* When a node to be pruned has offspring, the pruning * When a node to be pruned has offspring, the pruning procedure invokes
* procedure invokes itself recursively in order to prune the * itself recursively in order to prune the tree from the leaves upward.
* tree from the leaves upward. The statistics of the node * The statistics of the node being pruned are always added to the
* being pruned are always added to the corresponding data in * corresponding data in that node's parent. This retains the pruned
* that node's parent. This retains the pruned node's color * node's color characteristics for later averaging.
* characteristics for later averaging.
*/ */
void reduction() { void reduction() {
int threshold = 1; int threshold = 1;
while (colors > max_colors) { while (numColors > maxColors) {
colors = 0; numColors = firstColor;
threshold = root.reduce(threshold, Integer.MAX_VALUE); threshold = root.reduce(threshold, Integer.MAX_VALUE);
} }
} }
@ -416,76 +473,114 @@ public class Quantize {
*/ */
static class Search { static class Search {
int distance; int distance;
int color_number; int colorIndex;
} }
/* /*
* Procedure assignment generates the output image from the * Procedure assignment generates the output image from the pruned tree.
* pruned tree. The output image consists of two parts: (1) A * The output image consists of two parts: (1) A color map, which is an
* color map, which is an array of color descriptions (RGB * array of color descriptions (RGB triples) for each color present in
* triples) for each color present in the output image; (2) A * the output image; (2) A pixel array, which represents each pixel as
* pixel array, which represents each pixel as an index into * an index into the color map array.
* the color map array.
* *
* First, the assignment phase makes one pass over the pruned * First, the assignment phase makes one pass over the pruned color
* color description tree to establish the image's color map. * description tree to establish the image's color map. For each node
* For each node with n2 > 0, it divides Sr, Sg, and Sb by n2. * with n2 > 0, it divides Sr, Sg, and Sb by n2. This produces the mean
* This produces the mean color of all pixels that classify no * color of all pixels that classify no lower than this node. Each of
* lower than this node. Each of these colors becomes an entry * these colors becomes an entry in the color map.
* in the color map.
* *
* Finally, the assignment phase reclassifies each pixel in * Finally, the assignment phase reclassifies each pixel in the pruned
* the pruned tree to identify the deepest node containing the * tree to identify the deepest node containing the pixel's color. The
* pixel's color. The pixel's value in the pixel array becomes * pixel's value in the pixel array becomes the index of this node's
* the index of this node's mean color in the color map. * mean color in the color map.
*/ */
void assignment() { BufferedImage assignment() {
colormap = new int[colors]; colorMap = new byte[4][numColors];
colors = 0; if (addTransparency) {
root.colormap(); // if a transparency color is added, firstColor was set to 1,
// so color 0 can be used for this
colorMap[0][0] = 0;
colorMap[1][0] = 0;
colorMap[2][0] = 0;
colorMap[3][0] = 0;
}
numColors = firstColor;
root.mapColors();
int pixels[][] = this.pixels; // determine bit depth for palette
int depth;
for (depth = 1; depth <= 8; depth++)
if ((1 << depth) >= numColors)
break;
int width = pixels.length; // create the right color model, depending on transparency settings:
int height = pixels[0].length; IndexColorModel icm;
if (alphaToBitmask) {
if (addTransparency)
icm = new IndexColorModel(depth, numColors, colorMap[0],
colorMap[1], colorMap[2], 0);
else
icm = new IndexColorModel(depth, numColors, colorMap[0],
colorMap[1], colorMap[2]);
} else {
icm = new IndexColorModel(depth, numColors, colorMap[0],
colorMap[1], colorMap[2], colorMap[3]);
}
// create the indexed BufferedImage:
BufferedImage dest = new BufferedImage(source.getWidth(),
source.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, icm);
boolean firstOut = true;
if (dither)
new DiffusionFilterOp().filter(source, dest);
else {
Search search = new Search(); Search search = new Search();
// convert to indexed color // convert to indexed color
for (int x = width; x-- > 0; ) { byte[] dst = ((DataBufferByte) dest.getRaster().getDataBuffer()).getData();
for (int y = height; y-- > 0; ) {
int pixel = pixels[x][y];
int red = (pixel >> 16) & 0xFF;
int green = (pixel >> 8) & 0xFF;
int blue = (pixel >> 0) & 0xFF;
for (int i = 0; i < pixels.length; i++) {
int pixel = pixels[i];
int red = (pixel >> 16) & 0xff;
int green = (pixel >> 8) & 0xff;
int blue = (pixel >> 0) & 0xff;
int alpha = (pixel >> 24) & 0xff;
if (alphaToBitmask)
alpha = alpha < 0x80 ? 0 : 0xff;
if (alpha == 0 && addTransparency) {
dst[i] = 0; // transparency color is at 0
} else {
// walk the tree to find the cube containing that color // walk the tree to find the cube containing that color
Node node = root; Node node = root;
for ( ; ; ) { for (;;) {
int id = (((red > node.mid_red ? 1 : 0) << 0) | int id = (((red > node.midRed ? 1 : 0) << 0)
((green > node.mid_green ? 1 : 0) << 1) | | ((green > node.midGreen ? 1 : 0) << 1)
((blue > node.mid_blue ? 1 : 0) << 2) ); | ((blue > node.midBlue ? 1 : 0) << 2) | ((alpha > node.midAlpha ? 1
if (node.child[id] == null) { : 0) << 3));
if (node.children[id] == null) {
break; break;
} }
node = node.child[id]; node = node.children[id];
} }
if (QUICK) { if (QUICK) {
// if QUICK is set, just use that // if QUICK is set, just use that
// node. Strictly speaking, this isn't // node. Strictly speaking, this isn't
// necessarily best match. // necessarily best match.
pixels[x][y] = node.color_number; dst[i] = (byte) node.colorIndex;
} else { } else {
// Find the closest color. // Find the closest color.
search.distance = Integer.MAX_VALUE; search.distance = Integer.MAX_VALUE;
node.parent.closestColor(red, green, blue, search); node.parent.closestColor(red, green, blue, alpha,
pixels[x][y] = search.color_number; search);
dst[i] = (byte) search.colorIndex;
} }
} }
} }
} }
return dest;
}
/** /**
* A single Node in the tree. * A single Node in the tree.
@ -496,82 +591,87 @@ public class Quantize {
// parent node // parent node
Node parent; Node parent;
// child nodes // children nodes
Node child[]; Node children[];
int nchild; int numChildren;
// our index within our parent // our index within our parent
int id; int id;
// our level within the tree // our level within the tree
int level; int level;
// our color midpoint // our color midpoint
int mid_red; int midRed;
int mid_green; int midGreen;
int mid_blue; int midBlue;
int midAlpha;
// the pixel count for this node and all children // the pixel count for this node and all children
int number_pixels; int numPixels;
// the pixel count for this node // the pixel count for this node
int unique; int unique;
// the sum of all pixels contained in this node // the sum of all pixels contained in this node
int total_red; int totalRed;
int total_green; int totalGreen;
int total_blue; int totalBlue;
int totalAlpha;
// used to build the colormap // used to build the colorMap
int color_number; int colorIndex;
Node(Cube cube) { Node(Cube cube) {
this.cube = cube; this.cube = cube;
this.parent = this; this.parent = this;
this.child = new Node[8]; this.children = new Node[MAX_CHILDREN];
this.id = 0; this.id = 0;
this.level = 0; this.level = 0;
this.number_pixels = Integer.MAX_VALUE; this.numPixels = Integer.MAX_VALUE;
this.mid_red = (MAX_RGB + 1) >> 1; this.midRed = (MAX_RGB + 1) >> 1;
this.mid_green = (MAX_RGB + 1) >> 1; this.midGreen = (MAX_RGB + 1) >> 1;
this.mid_blue = (MAX_RGB + 1) >> 1; this.midBlue = (MAX_RGB + 1) >> 1;
this.midAlpha = (MAX_RGB + 1) >> 1;
} }
Node(Node parent, int id, int level) { Node(Node parent, int id, int level) {
this.cube = parent.cube; this.cube = parent.cube;
this.parent = parent; this.parent = parent;
this.child = new Node[8]; this.children = new Node[MAX_CHILDREN];
this.id = id; this.id = id;
this.level = level; this.level = level;
// add to the cube // add to the cube
++cube.nodes; ++cube.numNodes;
if (level == cube.depth) { if (level == cube.depth) {
++cube.colors; ++cube.numColors;
} }
// add to the parent // add to the parent
++parent.nchild; ++parent.numChildren;
parent.child[id] = this; parent.children[id] = this;
// figure out our midpoint // figure out our midpoint
int bi = (1 << (MAX_TREE_DEPTH - level)) >> 1; int bi = (1 << (MAX_TREE_DEPTH - level)) >> 1;
mid_red = parent.mid_red + ((id & 1) > 0 ? bi : -bi); midRed = parent.midRed + ((id & 1) > 0 ? bi : -bi);
mid_green = parent.mid_green + ((id & 2) > 0 ? bi : -bi); midGreen = parent.midGreen + ((id & 2) > 0 ? bi : -bi);
mid_blue = parent.mid_blue + ((id & 4) > 0 ? bi : -bi); midBlue = parent.midBlue + ((id & 4) > 0 ? bi : -bi);
midAlpha = parent.midAlpha + ((id & 8) > 0 ? bi : -bi);
} }
/** /**
* Remove this child node, and make sure our parent * Remove this children node, and make sure our parent absorbs our
* absorbs our pixel statistics. * pixel statistics.
*/ */
void pruneChild() { void pruneChild() {
--parent.nchild; --parent.numChildren;
parent.unique += unique; parent.unique += unique;
parent.total_red += total_red; parent.totalRed += totalRed;
parent.total_green += total_green; parent.totalGreen += totalGreen;
parent.total_blue += total_blue; parent.totalBlue += totalBlue;
parent.child[id] = null; parent.totalAlpha += totalAlpha;
--cube.nodes; parent.children[id] = null;
--cube.numNodes;
cube = null; cube = null;
parent = null; parent = null;
} }
@ -580,10 +680,10 @@ public class Quantize {
* Prune the lowest layer of the tree. * Prune the lowest layer of the tree.
*/ */
void pruneLevel() { void pruneLevel() {
if (nchild != 0) { if (numChildren != 0) {
for (int id = 0; id < 8; id++) { for (int id = 0; id < MAX_CHILDREN; id++) {
if (child[id] != null) { if (children[id] != null) {
child[id].pruneLevel(); children[id].pruneLevel();
} }
} }
} }
@ -593,78 +693,82 @@ public class Quantize {
} }
/** /**
* Remove any nodes that have fewer than threshold * Remove any nodes that have fewer than threshold pixels. Also, as
* pixels. Also, as long as we're walking the tree: * long as we're walking the tree: - figure out the color with the
* * fewest pixels - recalculate the total number of colors in the
* - figure out the color with the fewest pixels * tree
* - recalculate the total number of colors in the tree
*/ */
int reduce(int threshold, int next_threshold) { int reduce(int threshold, int nextThreshold) {
if (nchild != 0) { if (numChildren != 0) {
for (int id = 0; id < 8; id++) { for (int id = 0; id < MAX_CHILDREN; id++) {
if (child[id] != null) { if (children[id] != null) {
next_threshold = child[id].reduce(threshold, next_threshold); nextThreshold = children[id].reduce(threshold,
nextThreshold);
} }
} }
} }
if (number_pixels <= threshold) { if (numPixels <= threshold) {
pruneChild(); pruneChild();
} else { } else {
if (unique != 0) { if (unique != 0) {
cube.colors++; cube.numColors++;
} }
if (number_pixels < next_threshold) { if (numPixels < nextThreshold) {
next_threshold = number_pixels; nextThreshold = numPixels;
} }
} }
return next_threshold; return nextThreshold;
} }
/* /*
* colormap traverses the color cube tree and notes each * mapColors traverses the color cube tree and notes each colorMap
* colormap entry. A colormap entry is any node in the * entry. A colorMap entry is any node in the color cube tree where
* color cube tree where the number of unique colors is * the number of unique colors is not zero.
* not zero.
*/ */
void colormap() { void mapColors() {
if (nchild != 0) { if (numChildren != 0) {
for (int id = 0; id < 8; id++) { for (int id = 0; id < MAX_CHILDREN; id++) {
if (child[id] != null) { if (children[id] != null) {
child[id].colormap(); children[id].mapColors();
} }
} }
} }
if (unique != 0) { if (unique != 0) {
int r = ((total_red + (unique >> 1)) / unique); int add = unique >> 1;
int g = ((total_green + (unique >> 1)) / unique); cube.colorMap[0][cube.numColors] = (byte) ((totalRed + add) / unique);
int b = ((total_blue + (unique >> 1)) / unique); cube.colorMap[1][cube.numColors] = (byte) ((totalGreen + add) / unique);
cube.colormap[cube.colors] = ((( 0xFF) << 24) | cube.colorMap[2][cube.numColors] = (byte) ((totalBlue + add) / unique);
((r & 0xFF) << 16) | cube.colorMap[3][cube.numColors] = (byte) ((totalAlpha + add) / unique);
((g & 0xFF) << 8) | colorIndex = cube.numColors++;
((b & 0xFF) << 0));
color_number = cube.colors++;
} }
} }
/* ClosestColor traverses the color cube tree at a /*
* particular node and determines which colormap entry * ClosestColor traverses the color cube tree at a particular node
* best represents the input color. * and determines which colorMap entry best represents the input
* color.
*/ */
void closestColor(int red, int green, int blue, Search search) { void closestColor(int red, int green, int blue, int alpha,
if (nchild != 0) { Search search) {
for (int id = 0; id < 8; id++) { if (numChildren != 0) {
if (child[id] != null) { for (int id = 0; id < MAX_CHILDREN; id++) {
child[id].closestColor(red, green, blue, search); if (children[id] != null) {
children[id].closestColor(red, green, blue, alpha,
search);
} }
} }
} }
if (unique != 0) { if (unique != 0) {
int color = cube.colormap[color_number]; int distance = distance(
int distance = distance(color, red, green, blue); cube.colorMap[0][colorIndex] & 0xff,
cube.colorMap[1][colorIndex] & 0xff,
cube.colorMap[2][colorIndex] & 0xff,
cube.colorMap[3][colorIndex] & 0xff, red, green, blue,
alpha);
if (distance < search.distance) { if (distance < search.distance) {
search.distance = distance; search.distance = distance;
search.color_number = color_number; search.colorIndex = colorIndex;
} }
} }
} }
@ -672,10 +776,18 @@ public class Quantize {
/** /**
* Figure out the distance between this node and som color. * Figure out the distance between this node and som color.
*/ */
final static int distance(int color, int r, int g, int b) { final static int distance(int r1, int g1, int b1, int a1, int r2,
return (SQUARES[((color >> 16) & 0xFF) - r + MAX_RGB] + int g2, int b2, int a2) {
SQUARES[((color >> 8) & 0xFF) - g + MAX_RGB] + int da = a1 - a2;
SQUARES[((color >> 0) & 0xFF) - b + MAX_RGB]); int dr = r1 - r2;
int dg = g1 - g2;
int db = b1 - b2;
return da * da + dr * dr + dg * dg + db * db;
// return (SQUARES[r1 - r2 + MAX_RGB] +
// SQUARES[g1 - g2 + MAX_RGB] +
// SQUARES[b1 - b2 + MAX_RGB] +
// SQUARES[a1 - a2 + MAX_RGB]);
} }
public String toString() { public String toString() {
@ -688,11 +800,13 @@ public class Quantize {
buf.append(' '); buf.append(' ');
buf.append(level); buf.append(level);
buf.append(" ["); buf.append(" [");
buf.append(mid_red); buf.append(midRed);
buf.append(','); buf.append(',');
buf.append(mid_green); buf.append(midGreen);
buf.append(','); buf.append(',');
buf.append(mid_blue); buf.append(midBlue);
buf.append(',');
buf.append(midAlpha);
buf.append(']'); buf.append(']');
return new String(buf); return new String(buf);
} }