Part of the new Imaging libary: produces a BufferedImage, with and IndexColorModel, handles alpha channels and dithering.
This commit is contained in:
parent
b19370cdbc
commit
0f40d6142b
1 changed files with 360 additions and 246 deletions
|
@ -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,75 +473,113 @@ 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);
|
||||||
|
|
||||||
Search search = new Search();
|
boolean firstOut = true;
|
||||||
|
if (dither)
|
||||||
|
new DiffusionFilterOp().filter(source, dest);
|
||||||
|
else {
|
||||||
|
Search search = new Search();
|
||||||
|
// convert to indexed color
|
||||||
|
byte[] dst = ((DataBufferByte) dest.getRaster().getDataBuffer()).getData();
|
||||||
|
|
||||||
// convert to indexed color
|
for (int i = 0; i < pixels.length; i++) {
|
||||||
for (int x = width; x-- > 0; ) {
|
int pixel = pixels[i];
|
||||||
for (int y = height; y-- > 0; ) {
|
int red = (pixel >> 16) & 0xff;
|
||||||
int pixel = pixels[x][y];
|
int green = (pixel >> 8) & 0xff;
|
||||||
int red = (pixel >> 16) & 0xFF;
|
int blue = (pixel >> 0) & 0xff;
|
||||||
int green = (pixel >> 8) & 0xFF;
|
int alpha = (pixel >> 24) & 0xff;
|
||||||
int blue = (pixel >> 0) & 0xFF;
|
if (alphaToBitmask)
|
||||||
|
alpha = alpha < 0x80 ? 0 : 0xff;
|
||||||
|
|
||||||
// walk the tree to find the cube containing that color
|
if (alpha == 0 && addTransparency) {
|
||||||
Node node = root;
|
dst[i] = 0; // transparency color is at 0
|
||||||
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;
|
|
||||||
} else {
|
} else {
|
||||||
// Find the closest color.
|
// walk the tree to find the cube containing that color
|
||||||
search.distance = Integer.MAX_VALUE;
|
Node node = root;
|
||||||
node.parent.closestColor(red, green, blue, search);
|
for (;;) {
|
||||||
pixels[x][y] = search.color_number;
|
int id = (((red > node.midRed ? 1 : 0) << 0)
|
||||||
|
| ((green > node.midGreen ? 1 : 0) << 1)
|
||||||
|
| ((blue > node.midBlue ? 1 : 0) << 2) | ((alpha > node.midAlpha ? 1
|
||||||
|
: 0) << 3));
|
||||||
|
if (node.children[id] == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = node.children[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QUICK) {
|
||||||
|
// if QUICK is set, just use that
|
||||||
|
// node. Strictly speaking, this isn't
|
||||||
|
// necessarily best match.
|
||||||
|
dst[i] = (byte) node.colorIndex;
|
||||||
|
} else {
|
||||||
|
// Find the closest color.
|
||||||
|
search.distance = Integer.MAX_VALUE;
|
||||||
|
node.parent.closestColor(red, green, blue, alpha,
|
||||||
|
search);
|
||||||
|
dst[i] = (byte) search.colorIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue