Added new color quantizer and GIF encoder classes from http://www.gurge.com/amd/java/quantize/
This commit is contained in:
parent
45974b6664
commit
bd1d880506
2 changed files with 1179 additions and 0 deletions
478
src/helma/image/GIFEncoder.java
Normal file
478
src/helma/image/GIFEncoder.java
Normal file
|
@ -0,0 +1,478 @@
|
||||||
|
/*
|
||||||
|
* @(#)GIFEncoder.java 0.90 4/21/96 Adam Doppelt
|
||||||
|
*/
|
||||||
|
|
||||||
|
package helma.image;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GIFEncoder is a class which takes an image and saves it to a stream
|
||||||
|
* using the GIF file format (<A
|
||||||
|
* HREF="http://www.dcs.ed.ac.uk/%7Emxr/gfx/">Graphics Interchange
|
||||||
|
* Format</A>). A GIFEncoder
|
||||||
|
* is constructed with either an AWT Image (which must be fully
|
||||||
|
* loaded) or a set of RGB arrays. The image can be written out with a
|
||||||
|
* call to <CODE>Write</CODE>.<P>
|
||||||
|
*
|
||||||
|
* Three caveats:
|
||||||
|
* <UL>
|
||||||
|
* <LI>GIFEncoder will convert the image to indexed color upon
|
||||||
|
* construction. This will take some time, depending on the size of
|
||||||
|
* the image. Also, actually writing the image out (Write) will take
|
||||||
|
* time.<P>
|
||||||
|
*
|
||||||
|
* <LI>The image cannot have more than 256 colors, since GIF is an 8
|
||||||
|
* bit format. For a 24 bit to 8 bit quantization algorithm, see
|
||||||
|
* Graphics Gems II III.2 by Xialoin Wu. Or check out his <A
|
||||||
|
* HREF="http://www.csd.uwo.ca/faculty/wu/cq.c">C source</A>.<P>
|
||||||
|
*
|
||||||
|
* <LI>Since the image must be completely loaded into memory,
|
||||||
|
* GIFEncoder may have problems with large images. Attempting to
|
||||||
|
* encode an image which will not fit into memory will probably
|
||||||
|
* result in the following exception:<P>
|
||||||
|
* <CODE>java.awt.AWTException: Grabber returned false: 192</CODE><P>
|
||||||
|
* </UL><P>
|
||||||
|
*
|
||||||
|
* GIFEncoder is based upon gifsave.c, which was written and released
|
||||||
|
* by:<P>
|
||||||
|
* <CENTER>
|
||||||
|
* Sverre H. Huseby<BR>
|
||||||
|
* Bjoelsengt. 17<BR>
|
||||||
|
* N-0468 Oslo<BR>
|
||||||
|
* Norway<P>
|
||||||
|
*
|
||||||
|
* Phone: +47 2 230539<BR>
|
||||||
|
* sverrehu@ifi.uio.no<P>
|
||||||
|
* </CENTER>
|
||||||
|
* @version 0.90 21 Apr 1996
|
||||||
|
* @author <A HREF="http://www.cs.brown.edu/people/amd/">Adam Doppelt</A> */
|
||||||
|
public class GIFEncoder {
|
||||||
|
short width_, height_;
|
||||||
|
int numColors_;
|
||||||
|
byte pixels_[], colors_[];
|
||||||
|
|
||||||
|
ScreenDescriptor sd_;
|
||||||
|
ImageDescriptor id_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a GIFEncoder. The constructor will convert the image to
|
||||||
|
* an indexed color array. <B>This may take some time.</B><P>
|
||||||
|
*
|
||||||
|
* @param image The image to encode. The image <B>must</B> be
|
||||||
|
* completely loaded.
|
||||||
|
* @exception AWTException Will be thrown if the pixel grab fails. This
|
||||||
|
* can happen if Java runs out of memory. It may also indicate that the image
|
||||||
|
* contains more than 256 colors.
|
||||||
|
* */
|
||||||
|
public GIFEncoder(Image image) throws AWTException {
|
||||||
|
width_ = (short)image.getWidth(null);
|
||||||
|
height_ = (short)image.getHeight(null);
|
||||||
|
|
||||||
|
int values[] = new int[width_ * height_];
|
||||||
|
PixelGrabber grabber = new PixelGrabber(
|
||||||
|
image, 0, 0, width_, height_, values, 0, width_);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(grabber.grabPixels() != true)
|
||||||
|
throw new AWTException("Grabber returned false: " +
|
||||||
|
grabber.status());
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) { ; }
|
||||||
|
|
||||||
|
byte r[][] = new byte[width_][height_];
|
||||||
|
byte g[][] = new byte[width_][height_];
|
||||||
|
byte b[][] = new byte[width_][height_];
|
||||||
|
int index = 0;
|
||||||
|
for (int y = 0; y < height_; ++y)
|
||||||
|
for (int x = 0; x < width_; ++x) {
|
||||||
|
r[x][y] = (byte)((values[index] >> 16) & 0xFF);
|
||||||
|
g[x][y] = (byte)((values[index] >> 8) & 0xFF);
|
||||||
|
b[x][y] = (byte)((values[index]) & 0xFF);
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
ToIndexedColor(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a GIFEncoder. The constructor will convert the image to
|
||||||
|
* an indexed color array. <B>This may take some time.</B><P>
|
||||||
|
*
|
||||||
|
* Each array stores intensity values for the image. In other words,
|
||||||
|
* r[x][y] refers to the red intensity of the pixel at column x, row
|
||||||
|
* y.<P>
|
||||||
|
*
|
||||||
|
* @param r An array containing the red intensity values.
|
||||||
|
* @param g An array containing the green intensity values.
|
||||||
|
* @param b An array containing the blue intensity values.
|
||||||
|
*
|
||||||
|
* @exception AWTException Will be thrown if the image contains more than
|
||||||
|
* 256 colors.
|
||||||
|
* */
|
||||||
|
public GIFEncoder(byte r[][], byte g[][], byte b[][]) throws AWTException {
|
||||||
|
width_ = (short)(r.length);
|
||||||
|
height_ = (short)(r[0].length);
|
||||||
|
|
||||||
|
ToIndexedColor(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the image out to a stream in the GIF file format. This will
|
||||||
|
* be a single GIF87a image, non-interlaced, with no background color.
|
||||||
|
* <B>This may take some time.</B><P>
|
||||||
|
*
|
||||||
|
* @param output The stream to output to. This should probably be a
|
||||||
|
* buffered stream.
|
||||||
|
*
|
||||||
|
* @exception IOException Will be thrown if a write operation fails.
|
||||||
|
* */
|
||||||
|
public void Write(OutputStream output) throws IOException {
|
||||||
|
BitUtils.WriteString(output, "GIF87a");
|
||||||
|
|
||||||
|
ScreenDescriptor sd = new ScreenDescriptor(width_, height_,
|
||||||
|
numColors_);
|
||||||
|
sd.Write(output);
|
||||||
|
|
||||||
|
output.write(colors_, 0, colors_.length);
|
||||||
|
|
||||||
|
ImageDescriptor id = new ImageDescriptor(width_, height_, ',');
|
||||||
|
id.Write(output);
|
||||||
|
|
||||||
|
byte codesize = BitUtils.BitsNeeded(numColors_);
|
||||||
|
if (codesize == 1)
|
||||||
|
++codesize;
|
||||||
|
output.write(codesize);
|
||||||
|
|
||||||
|
LZWCompressor.LZWCompress(output, codesize, pixels_);
|
||||||
|
output.write(0);
|
||||||
|
|
||||||
|
id = new ImageDescriptor((byte)0, (byte)0, ';');
|
||||||
|
id.Write(output);
|
||||||
|
output.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ToIndexedColor(byte r[][], byte g[][],
|
||||||
|
byte b[][]) throws AWTException {
|
||||||
|
pixels_ = new byte[width_ * height_];
|
||||||
|
colors_ = new byte[256 * 3];
|
||||||
|
int colornum = 0;
|
||||||
|
for (int x = 0; x < width_; ++x) {
|
||||||
|
for (int y = 0; y < height_; ++y) {
|
||||||
|
int search;
|
||||||
|
for (search = 0; search < colornum; ++search)
|
||||||
|
if (colors_[search * 3] == r[x][y] &&
|
||||||
|
colors_[search * 3 + 1] == g[x][y] &&
|
||||||
|
colors_[search * 3 + 2] == b[x][y])
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (search > 255)
|
||||||
|
throw new AWTException("Too many colors.");
|
||||||
|
|
||||||
|
pixels_[y * width_ + x] = (byte)search;
|
||||||
|
|
||||||
|
if (search == colornum) {
|
||||||
|
colors_[search * 3] = r[x][y];
|
||||||
|
colors_[search * 3 + 1] = g[x][y];
|
||||||
|
colors_[search * 3 + 2] = b[x][y];
|
||||||
|
++colornum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numColors_ = 1 << BitUtils.BitsNeeded(colornum);
|
||||||
|
byte copy[] = new byte[numColors_ * 3];
|
||||||
|
System.arraycopy(colors_, 0, copy, 0, numColors_ * 3);
|
||||||
|
colors_ = copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class BitFile {
|
||||||
|
OutputStream output_;
|
||||||
|
byte buffer_[];
|
||||||
|
int index_, bitsLeft_;
|
||||||
|
|
||||||
|
public BitFile(OutputStream output) {
|
||||||
|
output_ = output;
|
||||||
|
buffer_ = new byte[256];
|
||||||
|
index_ = 0;
|
||||||
|
bitsLeft_ = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Flush() throws IOException {
|
||||||
|
int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1);
|
||||||
|
if (numBytes > 0) {
|
||||||
|
output_.write(numBytes);
|
||||||
|
output_.write(buffer_, 0, numBytes);
|
||||||
|
buffer_[0] = 0;
|
||||||
|
index_ = 0;
|
||||||
|
bitsLeft_ = 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteBits(int bits, int numbits) throws IOException {
|
||||||
|
int bitsWritten = 0;
|
||||||
|
int numBytes = 255;
|
||||||
|
do {
|
||||||
|
if ((index_ == 254 && bitsLeft_ == 0) || index_ > 254) {
|
||||||
|
output_.write(numBytes);
|
||||||
|
output_.write(buffer_, 0, numBytes);
|
||||||
|
|
||||||
|
buffer_[0] = 0;
|
||||||
|
index_ = 0;
|
||||||
|
bitsLeft_ = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numbits <= bitsLeft_) {
|
||||||
|
buffer_[index_] |= (bits & ((1 << numbits) - 1)) <<
|
||||||
|
(8 - bitsLeft_);
|
||||||
|
bitsWritten += numbits;
|
||||||
|
bitsLeft_ -= numbits;
|
||||||
|
numbits = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) <<
|
||||||
|
(8 - bitsLeft_);
|
||||||
|
bitsWritten += bitsLeft_;
|
||||||
|
bits >>= bitsLeft_;
|
||||||
|
numbits -= bitsLeft_;
|
||||||
|
buffer_[++index_] = 0;
|
||||||
|
bitsLeft_ = 8;
|
||||||
|
}
|
||||||
|
} while (numbits != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LZWStringTable {
|
||||||
|
private final static int RES_CODES = 2;
|
||||||
|
private final static short HASH_FREE = (short)0xFFFF;
|
||||||
|
private final static short NEXT_FIRST = (short)0xFFFF;
|
||||||
|
private final static int MAXBITS = 12;
|
||||||
|
private final static int MAXSTR = (1 << MAXBITS);
|
||||||
|
private final static short HASHSIZE = 9973;
|
||||||
|
private final static short HASHSTEP = 2039;
|
||||||
|
|
||||||
|
byte strChr_[];
|
||||||
|
short strNxt_[];
|
||||||
|
short strHsh_[];
|
||||||
|
short numStrings_;
|
||||||
|
|
||||||
|
public LZWStringTable() {
|
||||||
|
strChr_ = new byte[MAXSTR];
|
||||||
|
strNxt_ = new short[MAXSTR];
|
||||||
|
strHsh_ = new short[HASHSIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int AddCharString(short index, byte b) {
|
||||||
|
int hshidx;
|
||||||
|
|
||||||
|
if (numStrings_ >= MAXSTR)
|
||||||
|
return 0xFFFF;
|
||||||
|
|
||||||
|
hshidx = Hash(index, b);
|
||||||
|
while (strHsh_[hshidx] != HASH_FREE)
|
||||||
|
hshidx = (hshidx + HASHSTEP) % HASHSIZE;
|
||||||
|
|
||||||
|
strHsh_[hshidx] = numStrings_;
|
||||||
|
strChr_[numStrings_] = b;
|
||||||
|
strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST;
|
||||||
|
|
||||||
|
return numStrings_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short FindCharString(short index, byte b) {
|
||||||
|
int hshidx, nxtidx;
|
||||||
|
|
||||||
|
if (index == HASH_FREE)
|
||||||
|
return b;
|
||||||
|
|
||||||
|
hshidx = Hash(index, b);
|
||||||
|
while ((nxtidx = strHsh_[hshidx]) != HASH_FREE) {
|
||||||
|
if (strNxt_[nxtidx] == index && strChr_[nxtidx] == b)
|
||||||
|
return (short)nxtidx;
|
||||||
|
hshidx = (hshidx + HASHSTEP) % HASHSIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (short)0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearTable(int codesize) {
|
||||||
|
numStrings_ = 0;
|
||||||
|
|
||||||
|
for (int q = 0; q < HASHSIZE; q++) {
|
||||||
|
strHsh_[q] = HASH_FREE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = (1 << codesize) + RES_CODES;
|
||||||
|
for (int q = 0; q < w; q++)
|
||||||
|
AddCharString((short)0xFFFF, (byte)q);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public int Hash(short index, byte lastbyte) {
|
||||||
|
return ((int)((short)(lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LZWCompressor {
|
||||||
|
|
||||||
|
public static void LZWCompress(OutputStream output, int codesize,
|
||||||
|
byte toCompress[]) throws IOException {
|
||||||
|
byte c;
|
||||||
|
short index;
|
||||||
|
int clearcode, endofinfo, numbits, limit, errcode;
|
||||||
|
short prefix = (short)0xFFFF;
|
||||||
|
|
||||||
|
BitFile bitFile = new BitFile(output);
|
||||||
|
LZWStringTable strings = new LZWStringTable();
|
||||||
|
|
||||||
|
clearcode = 1 << codesize;
|
||||||
|
endofinfo = clearcode + 1;
|
||||||
|
|
||||||
|
numbits = codesize + 1;
|
||||||
|
limit = (1 << numbits) - 1;
|
||||||
|
|
||||||
|
strings.ClearTable(codesize);
|
||||||
|
bitFile.WriteBits(clearcode, numbits);
|
||||||
|
|
||||||
|
for (int loop = 0; loop < toCompress.length; ++loop) {
|
||||||
|
c = toCompress[loop];
|
||||||
|
if ((index = strings.FindCharString(prefix, c)) != -1)
|
||||||
|
prefix = index;
|
||||||
|
else {
|
||||||
|
bitFile.WriteBits(prefix, numbits);
|
||||||
|
if (strings.AddCharString(prefix, c) > limit) {
|
||||||
|
if (++numbits > 12) {
|
||||||
|
bitFile.WriteBits(clearcode, numbits - 1);
|
||||||
|
strings.ClearTable(codesize);
|
||||||
|
numbits = codesize + 1;
|
||||||
|
}
|
||||||
|
limit = (1 << numbits) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix = (short)((short)c & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix != -1)
|
||||||
|
bitFile.WriteBits(prefix, numbits);
|
||||||
|
|
||||||
|
bitFile.WriteBits(endofinfo, numbits);
|
||||||
|
bitFile.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScreenDescriptor {
|
||||||
|
public short localScreenWidth_, localScreenHeight_;
|
||||||
|
private byte byte_;
|
||||||
|
public byte backgroundColorIndex_, pixelAspectRatio_;
|
||||||
|
|
||||||
|
public ScreenDescriptor(short width, short height, int numColors) {
|
||||||
|
localScreenWidth_ = width;
|
||||||
|
localScreenHeight_ = height;
|
||||||
|
SetGlobalColorTableSize((byte)(BitUtils.BitsNeeded(numColors) - 1));
|
||||||
|
SetGlobalColorTableFlag((byte)1);
|
||||||
|
SetSortFlag((byte)0);
|
||||||
|
SetColorResolution((byte)7);
|
||||||
|
backgroundColorIndex_ = 0;
|
||||||
|
pixelAspectRatio_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(OutputStream output) throws IOException {
|
||||||
|
BitUtils.WriteWord(output, localScreenWidth_);
|
||||||
|
BitUtils.WriteWord(output, localScreenHeight_);
|
||||||
|
output.write(byte_);
|
||||||
|
output.write(backgroundColorIndex_);
|
||||||
|
output.write(pixelAspectRatio_);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetGlobalColorTableSize(byte num) {
|
||||||
|
byte_ |= (num & 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSortFlag(byte num) {
|
||||||
|
byte_ |= (num & 1) << 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetColorResolution(byte num) {
|
||||||
|
byte_ |= (num & 7) << 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetGlobalColorTableFlag(byte num) {
|
||||||
|
byte_ |= (num & 1) << 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageDescriptor {
|
||||||
|
public byte separator_;
|
||||||
|
public short leftPosition_, topPosition_, width_, height_;
|
||||||
|
private byte byte_;
|
||||||
|
|
||||||
|
public ImageDescriptor(short width, short height, char separator) {
|
||||||
|
separator_ = (byte)separator;
|
||||||
|
leftPosition_ = 0;
|
||||||
|
topPosition_ = 0;
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
SetLocalColorTableSize((byte)0);
|
||||||
|
SetReserved((byte)0);
|
||||||
|
SetSortFlag((byte)0);
|
||||||
|
SetInterlaceFlag((byte)0);
|
||||||
|
SetLocalColorTableFlag((byte)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(OutputStream output) throws IOException {
|
||||||
|
output.write(separator_);
|
||||||
|
BitUtils.WriteWord(output, leftPosition_);
|
||||||
|
BitUtils.WriteWord(output, topPosition_);
|
||||||
|
BitUtils.WriteWord(output, width_);
|
||||||
|
BitUtils.WriteWord(output, height_);
|
||||||
|
output.write(byte_);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLocalColorTableSize(byte num) {
|
||||||
|
byte_ |= (num & 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetReserved(byte num) {
|
||||||
|
byte_ |= (num & 3) << 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSortFlag(byte num) {
|
||||||
|
byte_ |= (num & 1) << 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInterlaceFlag(byte num) {
|
||||||
|
byte_ |= (num & 1) << 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLocalColorTableFlag(byte num) {
|
||||||
|
byte_ |= (num & 1) << 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BitUtils {
|
||||||
|
public static byte BitsNeeded(int n) {
|
||||||
|
byte ret = 1;
|
||||||
|
|
||||||
|
if (n-- == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
while ((n >>= 1) != 0)
|
||||||
|
++ret;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteWord(OutputStream output,
|
||||||
|
short w) throws IOException {
|
||||||
|
output.write(w & 0xFF);
|
||||||
|
output.write((w >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteString(OutputStream output,
|
||||||
|
String string) throws IOException {
|
||||||
|
for (int loop = 0; loop < string.length(); ++loop)
|
||||||
|
output.write((byte)(string.charAt(loop)));
|
||||||
|
}
|
||||||
|
}
|
701
src/helma/image/Quantize.java
Normal file
701
src/helma/image/Quantize.java
Normal file
|
@ -0,0 +1,701 @@
|
||||||
|
/*
|
||||||
|
* @(#)Quantize.java 0.90 9/19/00 Adam Doppelt
|
||||||
|
*/
|
||||||
|
package helma.image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An efficient color quantization algorithm, adapted from the C++
|
||||||
|
* implementation quantize.c in <a
|
||||||
|
* href="http://www.imagemagick.org/">ImageMagick</a>. The pixels for
|
||||||
|
* an image are placed into an oct tree. The oct tree is reduced in
|
||||||
|
* size, and the pixels from the original image are reassigned to the
|
||||||
|
* nodes in the reduced tree.<p>
|
||||||
|
*
|
||||||
|
* Here is the copyright notice from ImageMagick:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
% Permission is hereby granted, free of charge, to any person obtaining a %
|
||||||
|
% copy of this software and associated documentation files ("ImageMagick"), %
|
||||||
|
% to deal in ImageMagick without restriction, including without limitation %
|
||||||
|
% the rights to use, copy, modify, merge, publish, distribute, sublicense, %
|
||||||
|
% and/or sell copies of ImageMagick, and to permit persons to whom the %
|
||||||
|
% ImageMagick is furnished to do so, subject to the following conditions: %
|
||||||
|
% %
|
||||||
|
% The above copyright notice and this permission notice shall be included in %
|
||||||
|
% all copies or substantial portions of ImageMagick. %
|
||||||
|
% %
|
||||||
|
% The software is provided "as is", without warranty of any kind, express or %
|
||||||
|
% implied, including but not limited to the warranties of merchantability, %
|
||||||
|
% fitness for a particular purpose and noninfringement. In no event shall %
|
||||||
|
% E. I. du Pont de Nemours and Company be liable for any claim, damages or %
|
||||||
|
% other liability, whether in an action of contract, tort or otherwise, %
|
||||||
|
% arising from, out of or in connection with ImageMagick or the use or other %
|
||||||
|
% dealings in ImageMagick. %
|
||||||
|
% %
|
||||||
|
% Except as contained in this notice, the name of the E. I. du Pont de %
|
||||||
|
% Nemours and Company shall not be used in advertising or otherwise to %
|
||||||
|
% promote the sale, use or other dealings in ImageMagick without prior %
|
||||||
|
% written authorization from the E. I. du Pont de Nemours and Company. %
|
||||||
|
% %
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
</pre>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @version 0.90 19 Sep 2000
|
||||||
|
* @author <a href="http://www.gurge.com/amd/">Adam Doppelt</a>
|
||||||
|
*/
|
||||||
|
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 = true;
|
||||||
|
|
||||||
|
final static int MAX_RGB = 255;
|
||||||
|
final static int MAX_NODES = 266817;
|
||||||
|
final static int MAX_TREE_DEPTH = 8;
|
||||||
|
|
||||||
|
// these are precomputed in advance
|
||||||
|
static int SQUARES[];
|
||||||
|
static int SHIFT[];
|
||||||
|
|
||||||
|
static {
|
||||||
|
SQUARES = new int[MAX_RGB + MAX_RGB + 1];
|
||||||
|
for (int i= -MAX_RGB; i <= MAX_RGB; i++) {
|
||||||
|
SQUARES[i + MAX_RGB] = i * i;
|
||||||
|
}
|
||||||
|
|
||||||
|
SHIFT = new int[MAX_TREE_DEPTH + 1];
|
||||||
|
for (int i = 0; i < MAX_TREE_DEPTH + 1; ++i) {
|
||||||
|
SHIFT[i] = 1 << (15 - i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduce the image to the given number of colors. The pixels are
|
||||||
|
* reduced in place.
|
||||||
|
* @return The new color palette.
|
||||||
|
*/
|
||||||
|
public static int[] quantizeImage(int pixels[][], int max_colors) {
|
||||||
|
Cube cube = new Cube(pixels, max_colors);
|
||||||
|
cube.classification();
|
||||||
|
cube.reduction();
|
||||||
|
cube.assignment();
|
||||||
|
return cube.colormap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Cube {
|
||||||
|
int pixels[][];
|
||||||
|
int max_colors;
|
||||||
|
int colormap[];
|
||||||
|
|
||||||
|
Node root;
|
||||||
|
int depth;
|
||||||
|
|
||||||
|
// counter for the number of colors in the cube. this gets
|
||||||
|
// recalculated often.
|
||||||
|
int colors;
|
||||||
|
|
||||||
|
// counter for the number of nodes in the tree
|
||||||
|
int nodes;
|
||||||
|
|
||||||
|
Cube(int pixels[][], int max_colors) {
|
||||||
|
this.pixels = pixels;
|
||||||
|
this.max_colors = max_colors;
|
||||||
|
|
||||||
|
int i = max_colors;
|
||||||
|
// tree_depth = log max_colors
|
||||||
|
// 4
|
||||||
|
for (depth = 1; i != 0; depth++) {
|
||||||
|
i /= 4;
|
||||||
|
}
|
||||||
|
if (depth > 1) {
|
||||||
|
--depth;
|
||||||
|
}
|
||||||
|
if (depth > MAX_TREE_DEPTH) {
|
||||||
|
depth = MAX_TREE_DEPTH;
|
||||||
|
} else if (depth < 2) {
|
||||||
|
depth = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
root = new Node(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Procedure Classification begins by initializing a color
|
||||||
|
* description tree of sufficient depth to represent each
|
||||||
|
* possible input color in a leaf. However, it is impractical
|
||||||
|
* to generate a fully-formed color description tree in the
|
||||||
|
* classification phase for realistic values of cmax. If
|
||||||
|
* colors components in the input image are quantized to k-bit
|
||||||
|
* precision, so that cmax= 2k-1, the tree would need k levels
|
||||||
|
* below the root node to allow representing each possible
|
||||||
|
* input color in a leaf. This becomes prohibitive because the
|
||||||
|
* tree's total number of nodes is 1 + sum(i=1,k,8k).
|
||||||
|
*
|
||||||
|
* A complete tree would require 19,173,961 nodes for k = 8,
|
||||||
|
* cmax = 255. Therefore, to avoid building a fully populated
|
||||||
|
* tree, QUANTIZE: (1) Initializes data structures for nodes
|
||||||
|
* only as they are needed; (2) Chooses a maximum depth for
|
||||||
|
* the tree as a function of the desired number of colors in
|
||||||
|
* the output image (currently log2(colormap size)).
|
||||||
|
*
|
||||||
|
* For each pixel in the input image, classification scans
|
||||||
|
* downward from the root of the color description tree. At
|
||||||
|
* each level of the tree it identifies the single node which
|
||||||
|
* represents a cube in RGB space containing It updates the
|
||||||
|
* following data for each such node:
|
||||||
|
*
|
||||||
|
* number_pixels : Number of pixels whose color is contained
|
||||||
|
* in the RGB cube which this node represents;
|
||||||
|
*
|
||||||
|
* unique : Number of pixels whose color is not represented
|
||||||
|
* in a node at lower depth in the tree; initially, n2 = 0
|
||||||
|
* for all nodes except leaves of the tree.
|
||||||
|
*
|
||||||
|
* total_red/green/blue : Sums of the red, green, and blue
|
||||||
|
* component values for all pixels not classified at a lower
|
||||||
|
* depth. The combination of these sums and n2 will
|
||||||
|
* ultimately characterize the mean color of a set of pixels
|
||||||
|
* represented by this node.
|
||||||
|
*/
|
||||||
|
void classification() {
|
||||||
|
int pixels[][] = this.pixels;
|
||||||
|
|
||||||
|
int width = pixels.length;
|
||||||
|
int height = pixels[0].length;
|
||||||
|
|
||||||
|
// convert to indexed color
|
||||||
|
for (int x = width; x-- > 0; ) {
|
||||||
|
for (int y = height; y-- > 0; ) {
|
||||||
|
int pixel = pixels[x][y];
|
||||||
|
int 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
// convert to indexed color
|
||||||
|
for (int x = width; x-- > 0; ) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
} else {
|
||||||
|
// Find the closest color.
|
||||||
|
search.distance = Integer.MAX_VALUE;
|
||||||
|
node.parent.closestColor(red, green, blue, search);
|
||||||
|
pixels[x][y] = search.color_number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single Node in the tree.
|
||||||
|
*/
|
||||||
|
static class Node {
|
||||||
|
Cube cube;
|
||||||
|
|
||||||
|
// parent node
|
||||||
|
Node parent;
|
||||||
|
|
||||||
|
// child nodes
|
||||||
|
Node child[];
|
||||||
|
int nchild;
|
||||||
|
|
||||||
|
// our index within our parent
|
||||||
|
int id;
|
||||||
|
// our level within the tree
|
||||||
|
int level;
|
||||||
|
// our color midpoint
|
||||||
|
int mid_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue