Acme GIF encoder is slower for small files but faster for large ones, so we stick with it
This commit is contained in:
parent
5c46921eac
commit
d3d95a8288
1 changed files with 0 additions and 478 deletions
|
@ -1,478 +0,0 @@
|
|||
/*
|
||||
* @(#)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)));
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue